From 8785f642d08046e604f316a4102f755896341055 Mon Sep 17 00:00:00 2001
From: owent <admin@owent.net>
Date: Tue, 29 Nov 2022 14:19:03 +0800
Subject: [PATCH] Remove `EmitEvent` to follow
 https://github.com/open-telemetry/opentelemetry-specification/pull/2941

Signed-off-by: owent <admin@owent.net>
Fix `-Werror=suggest-override` and style

Signed-off-by: owent <admin@owent.net>
Fix ostream_log_test

Signed-off-by: owent <admin@owent.net>
Fix ostream print_value for `AttributeValue`

Signed-off-by: owent <admin@owent.net>
New Recordable of logs

Signed-off-by: owent <admin@owent.net>
Restore .vscode/launch.json

Signed-off-by: owent <admin@owent.net>
Fix warning.

Signed-off-by: owent <admin@owent.net>
Fix warnings in maintainer mode and ETW exporter

Signed-off-by: owent <admin@owent.net>
Add CHANGELOG

Signed-off-by: owent <admin@owent.net>
Allow to move 'nostd::unique_ptr<T>' into `nostd::shared_ptr<T>`

Signed-off-by: owent <admin@owent.net>
Do not use `std/type_traits.h` any more. Maybe we should remove this file later.

Signed-off-by: owent <admin@owent.net>
Allow to add rvalue into `CircularBuffer`

Signed-off-by: owent <admin@owent.net>
Finish new `LogRecord` for exporters.

Signed-off-by: owent <admin@owent.net>
Finish unit tests in API and SDK.
Exporters are still work in progress.

Signed-off-by: owent <admin@owent.net>
New `LogRecord` and `Recordable` implementations.

Signed-off-by: WenTao Ou <admin@owent.net>

Restore `nostd::unique_ptr` to `std::unique_ptr` in sdk and exporters.

Signed-off-by: WenTao Ou <admin@owent.net>

Fix feedback of comments and useless headers

Signed-off-by: WenTao Ou <admin@owent.net>

Optimize if branch in `ReadWriteLogRecord::GetResource` .

Signed-off-by: owent <admin@owent.net>
---
 .vscode/launch.json                           |  44 +--
 CHANGELOG.md                                  |   2 +
 .../common/key_value_iterable_view.h          |   8 +
 api/include/opentelemetry/logs/log_record.h   |  81 +++++
 api/include/opentelemetry/logs/logger.h       | 217 ++++++-------
 api/include/opentelemetry/logs/noop.h         |  21 +-
 api/include/opentelemetry/nostd/shared_ptr.h  |  15 +
 api/test/logs/logger_test.cc                  |  39 +--
 exporters/elasticsearch/BUILD                 |   1 +
 exporters/elasticsearch/CMakeLists.txt        |   2 +-
 .../elasticsearch/es_log_record_exporter.h    |   3 +-
 .../elasticsearch/es_log_recordable.h         | 194 +++---------
 .../src/es_log_record_exporter.cc             |   2 +-
 .../elasticsearch/src/es_log_recordable.cc    | 296 ++++++++++++++++++
 .../test/es_log_record_exporter_test.cc       |   1 -
 .../opentelemetry/exporters/etw/etw_logger.h  | 148 ++++++---
 .../exporters/ostream/common_utils.h          |  47 +++
 .../exporters/ostream/log_record_exporter.h   |   5 +-
 exporters/ostream/src/log_record_exporter.cc  |  45 ++-
 exporters/ostream/test/ostream_log_test.cc    | 118 +++----
 .../exporters/otlp/otlp_log_recordable.h      |  73 ++---
 .../otlp/otlp_populate_attribute_utils.h      |  10 +
 .../exporters/otlp/otlp_recordable_utils.h    |   6 +-
 .../otlp/src/otlp_grpc_log_record_exporter.cc |   3 +-
 .../otlp/src/otlp_http_log_record_exporter.cc |   3 +-
 exporters/otlp/src/otlp_log_recordable.cc     | 195 ++++++------
 .../otlp/src/otlp_populate_attribute_utils.cc | 123 +++++---
 exporters/otlp/src/otlp_recordable_utils.cc   |   4 +-
 .../otlp_grpc_log_record_exporter_test.cc     |   1 -
 .../otlp_http_log_record_exporter_test.cc     |   1 -
 .../otlp/test/otlp_log_recordable_test.cc     | 149 ++++-----
 .../sdk/common/circular_buffer.h              |   8 +
 .../logs/batch_log_record_processor_factory.h |   2 +
 sdk/include/opentelemetry/sdk/logs/exporter.h |   3 +-
 .../opentelemetry/sdk/logs/log_record.h       | 200 ------------
 sdk/include/opentelemetry/sdk/logs/logger.h   |  42 +--
 .../opentelemetry/sdk/logs/logger_context.h   |   2 +
 .../sdk/logs/logger_context_factory.h         |   2 +
 .../opentelemetry/sdk/logs/multi_recordable.h |  55 ++--
 .../opentelemetry/sdk/logs/processor.h        |   5 +-
 .../sdk/logs/read_write_log_record.h          | 191 +++++++++++
 .../sdk/logs/readable_log_record.h            | 121 +++++++
 .../opentelemetry/sdk/logs/recordable.h       |  63 +---
 .../sdk/logs/simple_log_record_processor.h    |   1 +
 .../simple_log_record_processor_factory.h     |   2 +
 sdk/src/logs/CMakeLists.txt                   |   4 +-
 sdk/src/logs/batch_log_record_processor.cc    |   2 +-
 sdk/src/logs/logger.cc                        |  84 +----
 sdk/src/logs/logger_provider.cc               |   2 +-
 sdk/src/logs/multi_log_record_processor.cc    |   2 +-
 sdk/src/logs/multi_recordable.cc              |  79 +++--
 sdk/src/logs/read_write_log_record.cc         | 174 ++++++++++
 sdk/src/logs/readable_log_record.cc           |  51 +++
 .../logs/batch_log_record_processor_test.cc   |  99 ++++--
 sdk/test/logs/log_record_test.cc              |  28 +-
 sdk/test/logs/logger_provider_sdk_test.cc     |  32 +-
 sdk/test/logs/logger_sdk_test.cc              |  79 ++++-
 .../logs/simple_log_record_processor_test.cc  |  64 +++-
 58 files changed, 2048 insertions(+), 1206 deletions(-)
 create mode 100644 api/include/opentelemetry/logs/log_record.h
 create mode 100644 exporters/elasticsearch/src/es_log_recordable.cc
 delete mode 100644 sdk/include/opentelemetry/sdk/logs/log_record.h
 create mode 100644 sdk/include/opentelemetry/sdk/logs/read_write_log_record.h
 create mode 100644 sdk/include/opentelemetry/sdk/logs/readable_log_record.h
 create mode 100644 sdk/src/logs/read_write_log_record.cc
 create mode 100644 sdk/src/logs/readable_log_record.cc

diff --git a/.vscode/launch.json b/.vscode/launch.json
index e9a256a518..3532ca4d2a 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,24 +1,24 @@
 {
-    "version": "0.2.0",
-    "configurations": [
-        {
-            "name": "Debug on Windows",
-            "type": "cppvsdbg",
-            "request": "launch",
-            "program": "${workspaceFolder}/build/<path-to-bin-file>",
-            "args": [],
-            "stopAtEntry": false,
-            "cwd": "${workspaceFolder}",
-            "environment": [],
-            "externalConsole": false
-        },
-        {
-            "name": "Debug on Linux",
-            "type": "gdb",
-            "request": "launch",
-            "target": "${workspaceFolder}/bazel-bin/<path to the bin file>",
-            "cwd": "${workspaceRoot}",
-            "valuesFormatting": "parseText"
-        }
-    ]
+  "version": "0.2.0",
+  "configurations": [
+      {
+          "name": "Debug on Windows",
+          "type": "cppvsdbg",
+          "request": "launch",
+          "program": "${workspaceFolder}/build/<path-to-bin-file>",
+          "args": [],
+          "stopAtEntry": false,
+          "cwd": "${workspaceFolder}",
+          "environment": [],
+          "externalConsole": false
+      },
+      {
+          "name": "Debug on Linux",
+          "type": "gdb",
+          "request": "launch",
+          "target": "${workspaceFolder}/bazel-bin/<path to the bin file>",
+          "cwd": "${workspaceRoot}",
+          "valuesFormatting": "parseText"
+      }
+  ]
 }
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fbe24799dd..3b2e3bb0dc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -69,6 +69,8 @@ exporters [#1828](https://github.com/open-telemetry/opentelemetry-cpp/pull/1828)
 * [BUILD] Add CMake OTELCPP_PROTO_PATH [#1730](https://github.com/open-telemetry/opentelemetry-cpp/pull/1730)
 * [SEMANTIC CONVENTIONS] Upgrade to version 1.15.0
   [#1761](https://github.com/open-telemetry/opentelemetry-cpp/pull/1761)
+* [LOGS SDK] New LogRecord and logs::Recordable implementations.
+  [#1766](https://github.com/open-telemetry/opentelemetry-cpp/pull/1766)
 
 Deprecation notes:
 
diff --git a/api/include/opentelemetry/common/key_value_iterable_view.h b/api/include/opentelemetry/common/key_value_iterable_view.h
index 2a0cbbc445..e688ec253f 100644
--- a/api/include/opentelemetry/common/key_value_iterable_view.h
+++ b/api/include/opentelemetry/common/key_value_iterable_view.h
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "opentelemetry/common/key_value_iterable.h"
+#include "opentelemetry/nostd/type_traits.h"
 #include "opentelemetry/nostd/utility.h"
 #include "opentelemetry/version.h"
 
@@ -73,5 +74,12 @@ class KeyValueIterableView final : public KeyValueIterable
 private:
   const T *container_;
 };
+
+template <class T, nostd::enable_if_t<detail::is_key_value_iterable<T>::value> * = nullptr>
+KeyValueIterableView<T> MakeKeyValueIterableView(const T &container) noexcept
+{
+  return KeyValueIterableView<T>(container);
+}
+
 }  // namespace common
 OPENTELEMETRY_END_NAMESPACE
diff --git a/api/include/opentelemetry/logs/log_record.h b/api/include/opentelemetry/logs/log_record.h
new file mode 100644
index 0000000000..7fb51c2d99
--- /dev/null
+++ b/api/include/opentelemetry/logs/log_record.h
@@ -0,0 +1,81 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+#ifdef ENABLE_LOGS_PREVIEW
+
+#  include "opentelemetry/common/attribute_value.h"
+#  include "opentelemetry/common/key_value_iterable.h"
+#  include "opentelemetry/common/timestamp.h"
+#  include "opentelemetry/logs/severity.h"
+#  include "opentelemetry/trace/span_id.h"
+#  include "opentelemetry/trace/trace_flags.h"
+#  include "opentelemetry/trace/trace_id.h"
+#  include "opentelemetry/version.h"
+
+OPENTELEMETRY_BEGIN_NAMESPACE
+namespace logs
+{
+/**
+ * Maintains a representation of a log in a format that can be processed by a recorder.
+ *
+ * This class is thread-compatible.
+ */
+class LogRecord
+{
+public:
+  virtual ~LogRecord() = default;
+
+  /**
+   * Set the timestamp for this log.
+   * @param timestamp the timestamp to set
+   */
+  virtual void SetTimestamp(opentelemetry::common::SystemTimestamp timestamp) noexcept = 0;
+
+  /**
+   * Set the observed timestamp for this log.
+   * @param timestamp the timestamp to set
+   */
+  virtual void SetObservedTimestamp(opentelemetry::common::SystemTimestamp timestamp) noexcept = 0;
+
+  /**
+   * Set the severity for this log.
+   * @param severity the severity of the event
+   */
+  virtual void SetSeverity(opentelemetry::logs::Severity severity) noexcept = 0;
+
+  /**
+   * Set body field for this log.
+   * @param message the body to set
+   */
+  virtual void SetBody(const opentelemetry::common::AttributeValue &message) noexcept = 0;
+
+  /**
+   * Set an attribute of a log.
+   * @param key the name of the attribute
+   * @param value the attribute value
+   */
+  virtual void SetAttribute(nostd::string_view key,
+                            const opentelemetry::common::AttributeValue &value) noexcept = 0;
+
+  /**
+   * Set the trace id for this log.
+   * @param trace_id the trace id to set
+   */
+  virtual void SetTraceId(const opentelemetry::trace::TraceId &trace_id) noexcept = 0;
+
+  /**
+   * Set the span id for this log.
+   * @param span_id the span id to set
+   */
+  virtual void SetSpanId(const opentelemetry::trace::SpanId &span_id) noexcept = 0;
+
+  /**
+   * Inject trace_flags for this log.
+   * @param trace_flags the trace flags to set
+   */
+  virtual void SetTraceFlags(const opentelemetry::trace::TraceFlags &trace_flags) noexcept = 0;
+};
+}  // namespace logs
+OPENTELEMETRY_END_NAMESPACE
+#endif
diff --git a/api/include/opentelemetry/logs/logger.h b/api/include/opentelemetry/logs/logger.h
index 32c3974a1f..a8342e3f71 100644
--- a/api/include/opentelemetry/logs/logger.h
+++ b/api/include/opentelemetry/logs/logger.h
@@ -13,11 +13,14 @@
 #  include "opentelemetry/common/key_value_iterable_view.h"
 #  include "opentelemetry/common/macros.h"
 #  include "opentelemetry/common/timestamp.h"
+#  include "opentelemetry/logs/log_record.h"
 #  include "opentelemetry/logs/severity.h"
 #  include "opentelemetry/nostd/shared_ptr.h"
 #  include "opentelemetry/nostd/span.h"
 #  include "opentelemetry/nostd/string_view.h"
 #  include "opentelemetry/nostd/type_traits.h"
+#  include "opentelemetry/nostd/unique_ptr.h"
+#  include "opentelemetry/trace/span_context.h"
 #  include "opentelemetry/trace/span_id.h"
 #  include "opentelemetry/trace/trace_flags.h"
 #  include "opentelemetry/trace/trace_id.h"
@@ -38,8 +41,40 @@ class Logger
   virtual const nostd::string_view GetName() noexcept = 0;
 
   /**
-   * Each of the following overloaded Log(...) methods
-   * creates a log message with the specific parameters passed.
+   * Create a Log Record object
+   *
+   * @return nostd::unique_ptr<LogRecord>
+   */
+  virtual nostd::unique_ptr<LogRecord> CreateLogRecord() noexcept = 0;
+
+  /**
+   * Emit a Log Record object
+   *
+   * @param log_record
+   */
+  virtual void EmitLogRecord(nostd::unique_ptr<LogRecord> &&log_record) noexcept = 0;
+
+  /**
+   * Emit a Log Record object with custom span context
+   *
+   * @param log_record
+   */
+  virtual void EmitLogRecord(nostd::unique_ptr<LogRecord> &&log_record,
+                             const trace::SpanContext &trace_context)
+  {
+    if (!log_record)
+    {
+      return;
+    }
+
+    log_record->SetSpanId(trace_context.span_id());
+    log_record->SetTraceId(trace_context.trace_id());
+    log_record->SetTraceFlags(trace_context.trace_flags());
+
+    EmitLogRecord(std::move(log_record));
+  }
+
+  /**
    *
    * @param severity the severity level of the log event.
    * @param message the string message of the log (perhaps support std::fmt or fmt-lib format).
@@ -51,45 +86,75 @@ class Logger
    * @param timestamp the timestamp the log record was created.
    * @throws No exceptions under any circumstances.
    */
-
-  /**
-   * The base Log(...) method that all other Log(...) overloaded methods will eventually call,
-   * in order to create a log record.
-   */
   virtual void Log(Severity severity,
                    nostd::string_view body,
                    const common::KeyValueIterable &attributes,
                    trace::TraceId trace_id,
                    trace::SpanId span_id,
                    trace::TraceFlags trace_flags,
-                   common::SystemTimestamp timestamp) noexcept = 0;
+                   common::SystemTimestamp timestamp) noexcept
+  {
+    nostd::unique_ptr<LogRecord> log_record = CreateLogRecord();
+    if (!log_record)
+    {
+      return;
+    }
+
+    log_record->SetSeverity(severity);
+    log_record->SetBody(body);
+    log_record->SetTimestamp(timestamp);
+
+    if (trace_id.IsValid())
+    {
+      log_record->SetTraceId(trace_id);
+      log_record->SetTraceFlags(trace_flags);
+    }
+
+    if (span_id.IsValid())
+    {
+      log_record->SetSpanId(span_id);
+    }
+
+    attributes.ForEachKeyValue(
+        [&log_record](nostd::string_view key, common::AttributeValue value) noexcept {
+          log_record->SetAttribute(key, value);
+          return true;
+        });
+
+    EmitLogRecord(std::move(log_record));
+  }
 
   /**
-   * Each of the following overloaded Log(...) methods
-   * creates a log message with the specific parameters passed.
    *
    * @param severity the severity level of the log event.
-   * @param name the name of the log event.
    * @param message the string message of the log (perhaps support std::fmt or fmt-lib format).
    * @param attributes the attributes, stored as a 2D list of key/value pairs, that are associated
    * with the log event.
-   * @param trace_id the trace id associated with the log event.
-   * @param span_id the span id associate with the log event.
-   * @param trace_flags the trace flags associated with the log event.
    * @param timestamp the timestamp the log record was created.
    * @throws No exceptions under any circumstances.
    */
-  OPENTELEMETRY_DEPRECATED_MESSAGE("name will be removed in the future")
   virtual void Log(Severity severity,
-                   OPENTELEMETRY_MAYBE_UNUSED nostd::string_view name,
                    nostd::string_view body,
                    const common::KeyValueIterable &attributes,
-                   trace::TraceId trace_id,
-                   trace::SpanId span_id,
-                   trace::TraceFlags trace_flags,
                    common::SystemTimestamp timestamp) noexcept
   {
-    Log(severity, body, attributes, trace_id, span_id, trace_flags, timestamp);
+    nostd::unique_ptr<LogRecord> log_record = CreateLogRecord();
+    if (!log_record)
+    {
+      return;
+    }
+
+    log_record->SetSeverity(severity);
+    log_record->SetBody(body);
+    log_record->SetTimestamp(timestamp);
+
+    attributes.ForEachKeyValue(
+        [&log_record](nostd::string_view key, common::AttributeValue value) noexcept {
+          log_record->SetAttribute(key, value);
+          return true;
+        });
+
+    EmitLogRecord(std::move(log_record));
   }
 
   /*** Overloaded methods for KeyValueIterables ***/
@@ -113,18 +178,12 @@ class Logger
 
   template <class T,
             nostd::enable_if_t<common::detail::is_key_value_iterable<T>::value> * = nullptr>
-  OPENTELEMETRY_DEPRECATED_MESSAGE("name will be removed in the future")
   void Log(Severity severity,
-           nostd::string_view name,
            nostd::string_view body,
            const T &attributes,
-           trace::TraceId trace_id,
-           trace::SpanId span_id,
-           trace::TraceFlags trace_flags,
            common::SystemTimestamp timestamp) noexcept
   {
-    Log(severity, name, body, common::KeyValueIterableView<T>(attributes), trace_id, span_id,
-        trace_flags, timestamp);
+    Log(severity, body, common::KeyValueIterableView<T>(attributes), timestamp);
   }
 
   void Log(Severity severity,
@@ -141,22 +200,6 @@ class Logger
                      trace_id, span_id, trace_flags, timestamp);
   }
 
-  OPENTELEMETRY_DEPRECATED_MESSAGE("name will be removed in the future")
-  void Log(Severity severity,
-           nostd::string_view name,
-           nostd::string_view body,
-           std::initializer_list<std::pair<nostd::string_view, common::AttributeValue>> attributes,
-           trace::TraceId trace_id,
-           trace::SpanId span_id,
-           trace::TraceFlags trace_flags,
-           common::SystemTimestamp timestamp) noexcept
-  {
-    return this->Log(severity, name, body,
-                     nostd::span<const std::pair<nostd::string_view, common::AttributeValue>>{
-                         attributes.begin(), attributes.end()},
-                     trace_id, span_id, trace_flags, timestamp);
-  }
-
   /** Wrapper methods that the user could call for convenience when logging **/
 
   /**
@@ -166,13 +209,9 @@ class Logger
    */
   void Log(Severity severity, nostd::string_view message) noexcept
   {
-    this->Log(severity, message, {}, {}, {}, {}, std::chrono::system_clock::now());
-  }
-
-  OPENTELEMETRY_DEPRECATED_MESSAGE("name will be removed in the future")
-  void Log(Severity severity, nostd::string_view name, nostd::string_view message) noexcept
-  {
-    this->Log(severity, name, message, {}, {}, {}, {}, std::chrono::system_clock::now());
+    this->Log(severity, message,
+              nostd::span<const std::pair<nostd::string_view, common::AttributeValue>>{},
+              std::chrono::system_clock::now());
   }
 
   /**
@@ -184,7 +223,7 @@ class Logger
             nostd::enable_if_t<common::detail::is_key_value_iterable<T>::value> * = nullptr>
   void Log(Severity severity, const T &attributes) noexcept
   {
-    this->Log(severity, "", attributes, {}, {}, {}, std::chrono::system_clock::now());
+    this->Log(severity, "", attributes, std::chrono::system_clock::now());
   }
 
   /**
@@ -197,7 +236,7 @@ class Logger
             nostd::enable_if_t<common::detail::is_key_value_iterable<T>::value> * = nullptr>
   void Log(Severity severity, nostd::string_view message, const T &attributes) noexcept
   {
-    this->Log(severity, message, attributes, {}, {}, {}, std::chrono::system_clock::now());
+    this->Log(severity, message, attributes, std::chrono::system_clock::now());
   }
 
   /**
@@ -209,7 +248,7 @@ class Logger
            std::initializer_list<std::pair<nostd::string_view, common::AttributeValue>>
                attributes) noexcept
   {
-    this->Log(severity, "", attributes, {}, {}, {}, std::chrono::system_clock::now());
+    this->Log(severity, "", attributes, std::chrono::system_clock::now());
   }
 
   /**
@@ -223,7 +262,7 @@ class Logger
            std::initializer_list<std::pair<nostd::string_view, common::AttributeValue>>
                attributes) noexcept
   {
-    this->Log(severity, message, attributes, {}, {}, {}, std::chrono::system_clock::now());
+    this->Log(severity, message, attributes, std::chrono::system_clock::now());
   }
 
   /**
@@ -234,7 +273,7 @@ class Logger
    */
   void Log(Severity severity, const common::KeyValueIterable &attributes) noexcept
   {
-    this->Log(severity, "", attributes, {}, {}, {}, std::chrono::system_clock::now());
+    this->Log(severity, "", attributes, std::chrono::system_clock::now());
   }
 
   /**
@@ -248,7 +287,7 @@ class Logger
            nostd::string_view message,
            const common::KeyValueIterable &attributes) noexcept
   {
-    this->Log(severity, message, attributes, {}, {}, {}, std::chrono::system_clock::now());
+    this->Log(severity, message, attributes, std::chrono::system_clock::now());
   }
 
   /** Trace severity overloads **/
@@ -259,17 +298,6 @@ class Logger
    */
   void Trace(nostd::string_view message) noexcept { this->Log(Severity::kTrace, message); }
 
-  /**
-   * Writes a log with a severity of trace.
-   * @param name The name of the log
-   * @param message The message to log
-   */
-  OPENTELEMETRY_DEPRECATED_MESSAGE("name will be removed in the future")
-  void Trace(nostd::string_view name, nostd::string_view message) noexcept
-  {
-    this->Log(Severity::kTrace, name, message);
-  }
-
   /**
    * Writes a log with a severity of trace.
    * @param attributes The attributes of the log as a key/value object
@@ -323,17 +351,6 @@ class Logger
    */
   void Debug(nostd::string_view message) noexcept { this->Log(Severity::kDebug, message); }
 
-  /**
-   * Writes a log with a severity of debug.
-   * @param name The name of the log
-   * @param message The message to log
-   */
-  OPENTELEMETRY_DEPRECATED_MESSAGE("name will be removed in the future")
-  void Debug(nostd::string_view name, nostd::string_view message) noexcept
-  {
-    this->Log(Severity::kDebug, name, message);
-  }
-
   /**
    * Writes a log with a severity of debug.
    * @param attributes The attributes of the log as a key/value object
@@ -387,17 +404,6 @@ class Logger
    */
   void Info(nostd::string_view message) noexcept { this->Log(Severity::kInfo, message); }
 
-  /**
-   * Writes a log with a severity of info.
-   * @param name The name of the log
-   * @param message The message to log
-   */
-  OPENTELEMETRY_DEPRECATED_MESSAGE("name will be removed in the future")
-  void Info(nostd::string_view name, nostd::string_view message) noexcept
-  {
-    this->Log(Severity::kInfo, name, message);
-  }
-
   /**
    * Writes a log with a severity of info.
    * @param attributes The attributes of the log as a key/value object
@@ -451,17 +457,6 @@ class Logger
    */
   void Warn(nostd::string_view message) noexcept { this->Log(Severity::kWarn, message); }
 
-  /**
-   * Writes a log with a severity of warn.
-   * @param name The name of the log
-   * @param message The message to log
-   */
-  OPENTELEMETRY_DEPRECATED_MESSAGE("name will be removed in the future")
-  void Warn(nostd::string_view name, nostd::string_view message) noexcept
-  {
-    this->Log(Severity::kWarn, name, message);
-  }
-
   /**
    * Writes a log with a severity of warn.
    * @param attributes The attributes of the log as a key/value object
@@ -515,17 +510,6 @@ class Logger
    */
   void Error(nostd::string_view message) noexcept { this->Log(Severity::kError, message); }
 
-  /**
-   * Writes a log with a severity of error.
-   * @param name The name of the log
-   * @param message The message to log
-   */
-  OPENTELEMETRY_DEPRECATED_MESSAGE("name will be removed in the future")
-  void Error(nostd::string_view name, nostd::string_view message) noexcept
-  {
-    this->Log(Severity::kError, name, message);
-  }
-
   /**
    * Writes a log with a severity of error.
    * @param attributes The attributes of the log as a key/value object
@@ -579,17 +563,6 @@ class Logger
    */
   void Fatal(nostd::string_view message) noexcept { this->Log(Severity::kFatal, message); }
 
-  /**
-   * Writes a log with a severity of fatal.
-   * @param name The name of the log
-   * @param message The message to log
-   */
-  OPENTELEMETRY_DEPRECATED_MESSAGE("name will be removed in the future")
-  void Fatal(nostd::string_view name, nostd::string_view message) noexcept
-  {
-    this->Log(Severity::kFatal, name, message);
-  }
-
   /**
    * Writes a log with a severity of fatal.
    * @param attributes The attributes of the log as a key/value object
diff --git a/api/include/opentelemetry/logs/noop.h b/api/include/opentelemetry/logs/noop.h
index 4663c86c18..d8941510da 100644
--- a/api/include/opentelemetry/logs/noop.h
+++ b/api/include/opentelemetry/logs/noop.h
@@ -41,24 +41,11 @@ class NoopLogger final : public Logger
 public:
   const nostd::string_view GetName() noexcept override { return "noop logger"; }
 
-  void Log(Severity /* severity */,
-           nostd::string_view /* body */,
-           const common::KeyValueIterable & /* attributes */,
-           trace::TraceId /* trace_id */,
-           trace::SpanId /* span_id */,
-           trace::TraceFlags /* trace_flags */,
-           common::SystemTimestamp /* timestamp */) noexcept override
-  {}
+  nostd::unique_ptr<LogRecord> CreateLogRecord() noexcept override { return nullptr; }
 
-  void Log(Severity /* severity */,
-           nostd::string_view /* name */,
-           nostd::string_view /* body */,
-           const common::KeyValueIterable & /* attributes */,
-           trace::TraceId /* trace_id */,
-           trace::SpanId /* span_id */,
-           trace::TraceFlags /* trace_flags */,
-           common::SystemTimestamp /* timestamp */) noexcept override
-  {}
+  using Logger::EmitLogRecord;
+
+  void EmitLogRecord(nostd::unique_ptr<LogRecord> &&) noexcept override {}
 };
 
 /**
diff --git a/api/include/opentelemetry/nostd/shared_ptr.h b/api/include/opentelemetry/nostd/shared_ptr.h
index 7afc30f9be..75b184f61c 100644
--- a/api/include/opentelemetry/nostd/shared_ptr.h
+++ b/api/include/opentelemetry/nostd/shared_ptr.h
@@ -9,6 +9,7 @@
 #  include <memory>
 #  include <utility>
 
+#  include "opentelemetry/nostd/unique_ptr.h"
 #  include "opentelemetry/version.h"
 
 OPENTELEMETRY_BEGIN_NAMESPACE
@@ -96,6 +97,20 @@ class shared_ptr
 
   shared_ptr(const shared_ptr &other) noexcept { other.wrapper().CopyTo(buffer_); }
 
+  shared_ptr(unique_ptr<T> &&other) noexcept
+  {
+    std::shared_ptr<T> ptr_(other.release());
+    new (buffer_.data) shared_ptr_wrapper{std::move(ptr_)};
+  }
+
+#  ifndef HAVE_CPP_STDLIB
+  shared_ptr(std::unique_ptr<T> &&other) noexcept
+  {
+    std::shared_ptr<T> ptr_(other.release());
+    new (buffer_.data) shared_ptr_wrapper{std::move(ptr_)};
+  }
+#  endif
+
   ~shared_ptr() { wrapper().~shared_ptr_wrapper(); }
 
   shared_ptr &operator=(shared_ptr &&other) noexcept
diff --git a/api/test/logs/logger_test.cc b/api/test/logs/logger_test.cc
index 0ce3a61037..48e9c375d3 100644
--- a/api/test/logs/logger_test.cc
+++ b/api/test/logs/logger_test.cc
@@ -66,19 +66,6 @@ TEST(Logger, LogMethodOverloads)
   logger->Log(Severity::kError, {{"key1", "value 1"}, {"key2", 2}});
   logger->Log(Severity::kFatal, "Logging an initializer list", {{"key1", "value 1"}, {"key2", 2}});
 
-  // Deprecated Log overloads
-  logger->Log(Severity::kTrace, "Log name", "Test log message");
-  logger->Log(Severity::kWarn, "Log name", "Logging a map", m, {}, {}, {},
-              std::chrono::system_clock::now());
-  logger->Log(Severity::kError, "Log name", "Logging a map", {{"key1", "value 1"}, {"key2", 2}}, {},
-              {}, {}, std::chrono::system_clock::now());
-  logger->Trace("Log name", "Test log message");
-  logger->Debug("Log name", "Test log message");
-  logger->Info("Log name", "Test log message");
-  logger->Warn("Log name", "Test log message");
-  logger->Error("Log name", "Test log message");
-  logger->Fatal("Log name", "Test log message");
-
   // Severity methods
   logger->Trace("Test log message");
   logger->Trace("Test log message", m);
@@ -117,24 +104,14 @@ class TestLogger : public Logger
 {
   const nostd::string_view GetName() noexcept override { return "test logger"; }
 
-  void Log(Severity /* severity */,
-           string_view /* body */,
-           const common::KeyValueIterable & /* attributes */,
-           trace::TraceId /* trace_id */,
-           trace::SpanId /* span_id */,
-           trace::TraceFlags /* trace_flags */,
-           common::SystemTimestamp /* timestamp */) noexcept override
-  {}
-
-  void Log(Severity /* severity */,
-           nostd::string_view /* name */,
-           nostd::string_view /* body */,
-           const common::KeyValueIterable & /* attributes */,
-           trace::TraceId /* trace_id */,
-           trace::SpanId /* span_id */,
-           trace::TraceFlags /* trace_flags */,
-           common::SystemTimestamp /* timestamp */) noexcept override
-  {}
+  nostd::unique_ptr<opentelemetry::logs::LogRecord> CreateLogRecord() noexcept override
+  {
+    return nullptr;
+  }
+
+  using Logger::EmitLogRecord;
+
+  void EmitLogRecord(nostd::unique_ptr<opentelemetry::logs::LogRecord> &&) noexcept override {}
 };
 
 // Define a basic LoggerProvider class that returns an instance of the logger class defined above
diff --git a/exporters/elasticsearch/BUILD b/exporters/elasticsearch/BUILD
index a13c881b0b..16fae2edab 100644
--- a/exporters/elasticsearch/BUILD
+++ b/exporters/elasticsearch/BUILD
@@ -4,6 +4,7 @@ cc_library(
     name = "es_log_record_exporter",
     srcs = [
         "src/es_log_record_exporter.cc",
+        "src/es_log_recordable.cc",
     ],
     hdrs = [
         "include/opentelemetry/exporters/elasticsearch/es_log_record_exporter.h",
diff --git a/exporters/elasticsearch/CMakeLists.txt b/exporters/elasticsearch/CMakeLists.txt
index ddae27cb71..90064e292d 100644
--- a/exporters/elasticsearch/CMakeLists.txt
+++ b/exporters/elasticsearch/CMakeLists.txt
@@ -1,5 +1,5 @@
 add_library(opentelemetry_exporter_elasticsearch_logs
-            src/es_log_record_exporter.cc)
+            src/es_log_record_exporter.cc src/es_log_recordable.cc)
 
 set_target_properties(opentelemetry_exporter_elasticsearch_logs
                       PROPERTIES EXPORT_NAME elasticsearch_log_record_exporter)
diff --git a/exporters/elasticsearch/include/opentelemetry/exporters/elasticsearch/es_log_record_exporter.h b/exporters/elasticsearch/include/opentelemetry/exporters/elasticsearch/es_log_record_exporter.h
index 2659851fd7..7a52810df0 100644
--- a/exporters/elasticsearch/include/opentelemetry/exporters/elasticsearch/es_log_record_exporter.h
+++ b/exporters/elasticsearch/include/opentelemetry/exporters/elasticsearch/es_log_record_exporter.h
@@ -9,7 +9,7 @@
 #  include "opentelemetry/ext/http/client/http_client_factory.h"
 #  include "opentelemetry/nostd/type_traits.h"
 #  include "opentelemetry/sdk/logs/exporter.h"
-#  include "opentelemetry/sdk/logs/log_record.h"
+#  include "opentelemetry/sdk/logs/recordable.h"
 
 #  include <time.h>
 #  include <iostream>
@@ -77,6 +77,7 @@ class ElasticsearchLogRecordExporter final : public opentelemetry::sdk::logs::Lo
 
   /**
    * Creates a recordable that stores the data in a JSON object
+   * @return a newly initialized Recordable object.
    */
   std::unique_ptr<opentelemetry::sdk::logs::Recordable> MakeRecordable() noexcept override;
 
diff --git a/exporters/elasticsearch/include/opentelemetry/exporters/elasticsearch/es_log_recordable.h b/exporters/elasticsearch/include/opentelemetry/exporters/elasticsearch/es_log_recordable.h
index 3e604ccf17..d6ed78d117 100644
--- a/exporters/elasticsearch/include/opentelemetry/exporters/elasticsearch/es_log_recordable.h
+++ b/exporters/elasticsearch/include/opentelemetry/exporters/elasticsearch/es_log_recordable.h
@@ -28,208 +28,92 @@ namespace logs
 class ElasticSearchRecordable final : public sdk::logs::Recordable
 {
 private:
-  // Define a JSON object that will be populated with the log data
-  nlohmann::json json_;
-
   /**
    * A helper method that writes a key/value pair under a specified name, the two names used here
    * being "attributes" and "resources"
    */
   void WriteKeyValue(nostd::string_view key,
                      const opentelemetry::common::AttributeValue &value,
-                     std::string name)
-  {
-    switch (value.index())
-    {
-      case common::AttributeType::kTypeBool:
-        json_[name][key.data()] = opentelemetry::nostd::get<bool>(value) ? true : false;
-        return;
-      case common::AttributeType::kTypeInt:
-        json_[name][key.data()] = opentelemetry::nostd::get<int>(value);
-        return;
-      case common::AttributeType::kTypeInt64:
-        json_[name][key.data()] = opentelemetry::nostd::get<int64_t>(value);
-        return;
-      case common::AttributeType::kTypeUInt:
-        json_[name][key.data()] = opentelemetry::nostd::get<unsigned int>(value);
-        return;
-      case common::AttributeType::kTypeUInt64:
-        json_[name][key.data()] = opentelemetry::nostd::get<uint64_t>(value);
-        return;
-      case common::AttributeType::kTypeDouble:
-        json_[name][key.data()] = opentelemetry::nostd::get<double>(value);
-        return;
-      case common::AttributeType::kTypeCString:
-        json_[name][key.data()] = opentelemetry::nostd::get<const char *>(value);
-        return;
-      case common::AttributeType::kTypeString:
-        json_[name][key.data()] =
-            opentelemetry::nostd::get<opentelemetry::nostd::string_view>(value).data();
-        return;
-      default:
-        return;
-    }
-  }
+                     std::string name);
 
   void WriteKeyValue(nostd::string_view key,
                      const opentelemetry::sdk::common::OwnedAttributeValue &value,
-                     std::string name)
-  {
-    namespace common = opentelemetry::sdk::common;
-    switch (value.index())
-    {
-      case common::kTypeBool:
-        json_[name][key.data()] = opentelemetry::nostd::get<bool>(value) ? true : false;
-        return;
-      case common::kTypeInt:
-        json_[name][key.data()] = opentelemetry::nostd::get<int>(value);
-        return;
-      case common::kTypeInt64:
-        json_[name][key.data()] = opentelemetry::nostd::get<int64_t>(value);
-        return;
-      case common::kTypeUInt:
-        json_[name][key.data()] = opentelemetry::nostd::get<unsigned int>(value);
-        return;
-      case common::kTypeUInt64:
-        json_[name][key.data()] = opentelemetry::nostd::get<uint64_t>(value);
-        return;
-      case common::kTypeDouble:
-        json_[name][key.data()] = opentelemetry::nostd::get<double>(value);
-        return;
-      case common::kTypeString:
-        json_[name][key.data()] = opentelemetry::nostd::get<std::string>(value).data();
-        return;
-      default:
-        return;
-    }
-  }
+                     std::string name);
+
+  void WriteValue(const opentelemetry::common::AttributeValue &value, std::string name);
 
 public:
   /**
-   * Set the severity for this log.
-   * @param severity the severity of the event
+   * Returns a JSON object contain the log information
    */
-  void SetSeverity(opentelemetry::logs::Severity severity) noexcept override
-  {
-    // Convert the severity enum to a string
-    std::uint32_t severity_index = static_cast<std::uint32_t>(severity);
-    if (severity_index >= std::extent<decltype(opentelemetry::logs::SeverityNumToText)>::value)
-    {
-      std::stringstream sout;
-      sout << "Invalid severity(" << severity_index << ")";
-      json_["severity"] = sout.str();
-    }
-    else
-    {
-      json_["severity"] = opentelemetry::logs::SeverityNumToText[severity_index];
-    }
-  }
+  nlohmann::json GetJSON() noexcept;
 
   /**
-   * Set body field for this log.
-   * @param message the body to set
+   * Set the timestamp for this log.
+   * @param timestamp the timestamp to set
    */
-  void SetBody(nostd::string_view message) noexcept override { json_["body"] = message.data(); }
+  void SetTimestamp(opentelemetry::common::SystemTimestamp timestamp) noexcept override;
 
   /**
-   * Set Resource of this log
-   * @param Resource the resource to set
+   * Set the observed timestamp for this log.
+   * @param timestamp the timestamp to set
    */
-  void SetResource(const opentelemetry::sdk::resource::Resource &resource) noexcept override
-  {
-    for (auto &kv : resource.GetAttributes())
-    {
-      WriteKeyValue(kv.first, kv.second, "resource");
-    }
-  }
+  void SetObservedTimestamp(opentelemetry::common::SystemTimestamp timestamp) noexcept override;
 
   /**
-   * Set an attribute of a log.
-   * @param key the key of the attribute
-   * @param value the attribute value
+   * Set the severity for this log.
+   * @param severity the severity of the event
    */
-  void SetAttribute(nostd::string_view key,
-                    const opentelemetry::common::AttributeValue &value) noexcept override
-  {
-    WriteKeyValue(key, value, "attributes");
-  }
+  void SetSeverity(opentelemetry::logs::Severity severity) noexcept override;
+
+  /**
+   * Set body field for this log.
+   * @param message the body to set
+   */
+  void SetBody(const opentelemetry::common::AttributeValue &message) noexcept override;
 
   /**
-   * Set trace id for this log.
+   * Set the trace id for this log.
    * @param trace_id the trace id to set
    */
-  void SetTraceId(opentelemetry::trace::TraceId trace_id) noexcept override
-  {
-    char trace_buf[32];
-    trace_id.ToLowerBase16(trace_buf);
-    json_["traceid"] = std::string(trace_buf, sizeof(trace_buf));
-  }
+  void SetTraceId(const opentelemetry::trace::TraceId &trace_id) noexcept override;
 
   /**
-   * Set span id for this log.
+   * Set the span id for this log.
    * @param span_id the span id to set
    */
-  virtual void SetSpanId(opentelemetry::trace::SpanId span_id) noexcept override
-  {
-    char span_buf[16];
-    span_id.ToLowerBase16(span_buf);
-    json_["spanid"] = std::string(span_buf, sizeof(span_buf));
-  }
+  void SetSpanId(const opentelemetry::trace::SpanId &span_id) noexcept override;
 
   /**
-   * Inject a trace_flags  for this log.
-   * @param trace_flags the span id to set
+   * Inject trace_flags for this log.
+   * @param trace_flags the trace flags to set
    */
-  void SetTraceFlags(opentelemetry::trace::TraceFlags trace_flags) noexcept override
-  {
-    char flag_buf[2];
-    trace_flags.ToLowerBase16(flag_buf);
-    json_["traceflags"] = std::string(flag_buf, sizeof(flag_buf));
-  }
+  void SetTraceFlags(const opentelemetry::trace::TraceFlags &trace_flags) noexcept override;
 
   /**
-   * Set the timestamp for this log.
-   * @param timestamp the timestamp of the event
+   * Set an attribute of a log.
+   * @param key the name of the attribute
+   * @param value the attribute value
    */
-  void SetTimestamp(common::SystemTimestamp timestamp) noexcept override
-  {
-    json_["timestamp"] = timestamp.time_since_epoch().count();
-  }
+  void SetAttribute(nostd::string_view key,
+                    const opentelemetry::common::AttributeValue &value) noexcept override;
 
   /**
-   * Returns a JSON object contain the log information
+   * Set Resource of this log
+   * @param Resource the resource to set
    */
-  nlohmann::json GetJSON() noexcept { return json_; }
+  void SetResource(const opentelemetry::sdk::resource::Resource &resource) noexcept override;
 
   /**
    * Set instrumentation_scope for this log.
    * @param instrumentation_scope the instrumentation scope to set
    */
   void SetInstrumentationScope(const opentelemetry::sdk::instrumentationscope::InstrumentationScope
-                                   &instrumentation_scope) noexcept override
-  {
-    json_["name"]          = instrumentation_scope.GetName();
-    instrumentation_scope_ = &instrumentation_scope;
-  }
-
-  /** Returns the associated instruementation library */
-  OPENTELEMETRY_DEPRECATED_MESSAGE("Please use GetInstrumentationScope instead")
-  const opentelemetry::sdk::instrumentationscope::InstrumentationScope &GetInstrumentationLibrary()
-      const noexcept
-  {
-    return GetInstrumentationScope();
-  }
-
-  /** Returns the associated instruementation library */
-  const opentelemetry::sdk::instrumentationscope::InstrumentationScope &GetInstrumentationScope()
-      const noexcept
-  {
-    return *instrumentation_scope_;
-  }
+                                   &instrumentation_scope) noexcept override;
 
 private:
-  const opentelemetry::sdk::instrumentationscope::InstrumentationScope *instrumentation_scope_ =
-      nullptr;
+  // Define a JSON object that will be populated with the log data
+  nlohmann::json json_;
 };
 }  // namespace logs
 }  // namespace exporter
diff --git a/exporters/elasticsearch/src/es_log_record_exporter.cc b/exporters/elasticsearch/src/es_log_record_exporter.cc
index 3bd4fc7b0c..c440be685c 100644
--- a/exporters/elasticsearch/src/es_log_record_exporter.cc
+++ b/exporters/elasticsearch/src/es_log_record_exporter.cc
@@ -301,7 +301,7 @@ ElasticsearchLogRecordExporter::ElasticsearchLogRecordExporter(
 
 std::unique_ptr<sdklogs::Recordable> ElasticsearchLogRecordExporter::MakeRecordable() noexcept
 {
-  return std::unique_ptr<sdklogs::Recordable>(new ElasticSearchRecordable);
+  return std::unique_ptr<sdklogs::Recordable>(new ElasticSearchRecordable());
 }
 
 sdk::common::ExportResult ElasticsearchLogRecordExporter::Export(
diff --git a/exporters/elasticsearch/src/es_log_recordable.cc b/exporters/elasticsearch/src/es_log_recordable.cc
new file mode 100644
index 0000000000..03c6c640e5
--- /dev/null
+++ b/exporters/elasticsearch/src/es_log_recordable.cc
@@ -0,0 +1,296 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+#ifdef ENABLE_LOGS_PREVIEW
+
+#  include "opentelemetry/exporters/elasticsearch/es_log_recordable.h"
+
+OPENTELEMETRY_BEGIN_NAMESPACE
+namespace exporter
+{
+namespace logs
+{
+void ElasticSearchRecordable::WriteKeyValue(nostd::string_view key,
+                                            const opentelemetry::common::AttributeValue &value,
+                                            std::string name)
+{
+  switch (value.index())
+  {
+    case common::AttributeType::kTypeBool:
+      json_[name][key.data()] = opentelemetry::nostd::get<bool>(value) ? true : false;
+      return;
+    case common::AttributeType::kTypeInt:
+      json_[name][key.data()] = opentelemetry::nostd::get<int>(value);
+      return;
+    case common::AttributeType::kTypeInt64:
+      json_[name][key.data()] = opentelemetry::nostd::get<int64_t>(value);
+      return;
+    case common::AttributeType::kTypeUInt:
+      json_[name][key.data()] = opentelemetry::nostd::get<unsigned int>(value);
+      return;
+    case common::AttributeType::kTypeUInt64:
+      json_[name][key.data()] = opentelemetry::nostd::get<uint64_t>(value);
+      return;
+    case common::AttributeType::kTypeDouble:
+      json_[name][key.data()] = opentelemetry::nostd::get<double>(value);
+      return;
+    case common::AttributeType::kTypeCString:
+      json_[name][key.data()] = opentelemetry::nostd::get<const char *>(value);
+      return;
+    case common::AttributeType::kTypeString:
+      json_[name][key.data()] =
+          opentelemetry::nostd::get<opentelemetry::nostd::string_view>(value).data();
+      return;
+    default:
+      return;
+  }
+}
+
+void ElasticSearchRecordable::WriteKeyValue(
+    nostd::string_view key,
+    const opentelemetry::sdk::common::OwnedAttributeValue &value,
+    std::string name)
+{
+  namespace common = opentelemetry::sdk::common;
+  switch (value.index())
+  {
+    case common::kTypeBool:
+      json_[name][key.data()] = opentelemetry::nostd::get<bool>(value) ? true : false;
+      return;
+    case common::kTypeInt:
+      json_[name][key.data()] = opentelemetry::nostd::get<int>(value);
+      return;
+    case common::kTypeInt64:
+      json_[name][key.data()] = opentelemetry::nostd::get<int64_t>(value);
+      return;
+    case common::kTypeUInt:
+      json_[name][key.data()] = opentelemetry::nostd::get<unsigned int>(value);
+      return;
+    case common::kTypeUInt64:
+      json_[name][key.data()] = opentelemetry::nostd::get<uint64_t>(value);
+      return;
+    case common::kTypeDouble:
+      json_[name][key.data()] = opentelemetry::nostd::get<double>(value);
+      return;
+    case common::kTypeString:
+      json_[name][key.data()] = opentelemetry::nostd::get<std::string>(value).data();
+      return;
+    default:
+      return;
+  }
+}
+
+void ElasticSearchRecordable::WriteValue(const opentelemetry::common::AttributeValue &value,
+                                         std::string name)
+{
+
+  // Assert size of variant to ensure that this method gets updated if the variant
+  // definition changes
+
+  if (nostd::holds_alternative<bool>(value))
+  {
+    json_[name] = opentelemetry::nostd::get<bool>(value) ? true : false;
+  }
+  else if (nostd::holds_alternative<int>(value))
+  {
+    json_[name] = opentelemetry::nostd::get<int>(value);
+  }
+  else if (nostd::holds_alternative<int64_t>(value))
+  {
+    json_[name] = opentelemetry::nostd::get<int64_t>(value);
+  }
+  else if (nostd::holds_alternative<unsigned int>(value))
+  {
+    json_[name] = opentelemetry::nostd::get<unsigned int>(value);
+  }
+  else if (nostd::holds_alternative<uint64_t>(value))
+  {
+    json_[name] = opentelemetry::nostd::get<uint64_t>(value);
+  }
+  else if (nostd::holds_alternative<double>(value))
+  {
+    json_[name] = opentelemetry::nostd::get<double>(value);
+  }
+  else if (nostd::holds_alternative<const char *>(value))
+  {
+    json_[name] = std::string(nostd::get<const char *>(value));
+  }
+  else if (nostd::holds_alternative<nostd::string_view>(value))
+  {
+    json_[name] = static_cast<std::string>(opentelemetry::nostd::get<nostd::string_view>(value));
+  }
+  else if (nostd::holds_alternative<nostd::span<const uint8_t>>(value))
+  {
+    nlohmann::json array_value = nlohmann::json::array();
+    for (const auto &val : nostd::get<nostd::span<const uint8_t>>(value))
+    {
+      array_value.push_back(val);
+    }
+    json_[name] = array_value;
+  }
+  else if (nostd::holds_alternative<nostd::span<const bool>>(value))
+  {
+    nlohmann::json array_value = nlohmann::json::array();
+    for (const auto &val : nostd::get<nostd::span<const bool>>(value))
+    {
+      array_value.push_back(val);
+    }
+    json_[name] = array_value;
+  }
+  else if (nostd::holds_alternative<nostd::span<const int>>(value))
+  {
+    nlohmann::json array_value = nlohmann::json::array();
+    for (const auto &val : nostd::get<nostd::span<const int>>(value))
+    {
+      array_value.push_back(val);
+    }
+    json_[name] = array_value;
+  }
+  else if (nostd::holds_alternative<nostd::span<const int64_t>>(value))
+  {
+    nlohmann::json array_value = nlohmann::json::array();
+    for (const auto &val : nostd::get<nostd::span<const int64_t>>(value))
+    {
+      array_value.push_back(val);
+    }
+    json_[name] = array_value;
+  }
+  else if (nostd::holds_alternative<nostd::span<const unsigned int>>(value))
+  {
+    nlohmann::json array_value = nlohmann::json::array();
+    for (const auto &val : nostd::get<nostd::span<const unsigned int>>(value))
+    {
+      array_value.push_back(val);
+    }
+    json_[name] = array_value;
+  }
+  else if (nostd::holds_alternative<nostd::span<const uint64_t>>(value))
+  {
+    nlohmann::json array_value = nlohmann::json::array();
+    for (const auto &val : nostd::get<nostd::span<const uint64_t>>(value))
+    {
+      array_value.push_back(val);
+    }
+    json_[name] = array_value;
+  }
+  else if (nostd::holds_alternative<nostd::span<const double>>(value))
+  {
+    nlohmann::json array_value = nlohmann::json::array();
+    for (const auto &val : nostd::get<nostd::span<const double>>(value))
+    {
+      array_value.push_back(val);
+    }
+    json_[name] = array_value;
+  }
+  else if (nostd::holds_alternative<nostd::span<const nostd::string_view>>(value))
+  {
+    nlohmann::json array_value = nlohmann::json::array();
+    for (const auto &val : nostd::get<nostd::span<const nostd::string_view>>(value))
+    {
+      array_value.push_back(static_cast<std::string>(val));
+    }
+    json_[name] = array_value;
+  }
+}
+
+nlohmann::json ElasticSearchRecordable::GetJSON() noexcept
+{
+  return json_;
+}
+
+void ElasticSearchRecordable::SetTimestamp(
+    opentelemetry::common::SystemTimestamp timestamp) noexcept
+{
+  json_["timestamp"] = timestamp.time_since_epoch().count();
+}
+
+void ElasticSearchRecordable::SetObservedTimestamp(
+    opentelemetry::common::SystemTimestamp timestamp) noexcept
+{
+  json_["observedtimestamp"] = timestamp.time_since_epoch().count();
+}
+
+void ElasticSearchRecordable::SetSeverity(opentelemetry::logs::Severity severity) noexcept
+{
+  // Convert the severity enum to a string
+  std::uint32_t severity_index = static_cast<std::uint32_t>(severity);
+  if (severity_index >= std::extent<decltype(opentelemetry::logs::SeverityNumToText)>::value)
+  {
+    std::stringstream sout;
+    sout << "Invalid severity(" << severity_index << ")";
+    json_["severity"] = sout.str();
+  }
+  else
+  {
+    json_["severity"] = opentelemetry::logs::SeverityNumToText[severity_index];
+  }
+}
+
+void ElasticSearchRecordable::SetBody(const opentelemetry::common::AttributeValue &message) noexcept
+{
+  WriteValue(message, "body");
+}
+
+void ElasticSearchRecordable::SetTraceId(const opentelemetry::trace::TraceId &trace_id) noexcept
+{
+  if (trace_id.IsValid())
+  {
+    char trace_buf[32];
+    trace_id.ToLowerBase16(trace_buf);
+    json_["traceid"] = std::string(trace_buf, sizeof(trace_buf));
+  }
+  else
+  {
+    json_.erase("traceid");
+  }
+}
+
+void ElasticSearchRecordable::SetSpanId(const opentelemetry::trace::SpanId &span_id) noexcept
+{
+  if (span_id.IsValid())
+  {
+    char span_buf[16];
+    span_id.ToLowerBase16(span_buf);
+    json_["spanid"] = std::string(span_buf, sizeof(span_buf));
+  }
+  else
+  {
+    json_.erase("spanid");
+  }
+}
+
+void ElasticSearchRecordable::SetTraceFlags(
+    const opentelemetry::trace::TraceFlags &trace_flags) noexcept
+{
+  char flag_buf[2];
+  trace_flags.ToLowerBase16(flag_buf);
+  json_["traceflags"] = std::string(flag_buf, sizeof(flag_buf));
+}
+
+void ElasticSearchRecordable::SetAttribute(
+    nostd::string_view key,
+    const opentelemetry::common::AttributeValue &value) noexcept
+{
+  WriteKeyValue(key, value, "attributes");
+}
+
+void ElasticSearchRecordable::SetResource(
+    const opentelemetry::sdk::resource::Resource &resource) noexcept
+{
+  for (auto &attribute : resource.GetAttributes())
+  {
+    WriteKeyValue(attribute.first, attribute.second, "resource");
+  }
+}
+
+void ElasticSearchRecordable::SetInstrumentationScope(
+    const opentelemetry::sdk::instrumentationscope::InstrumentationScope
+        &instrumentation_scope) noexcept
+{
+  json_["name"] = instrumentation_scope.GetName();
+}
+
+}  // namespace logs
+}  // namespace exporter
+OPENTELEMETRY_END_NAMESPACE
+#endif
diff --git a/exporters/elasticsearch/test/es_log_record_exporter_test.cc b/exporters/elasticsearch/test/es_log_record_exporter_test.cc
index 4cd3b5064f..cc9af2ee2d 100644
--- a/exporters/elasticsearch/test/es_log_record_exporter_test.cc
+++ b/exporters/elasticsearch/test/es_log_record_exporter_test.cc
@@ -7,7 +7,6 @@
 #  include "opentelemetry/ext/http/server/http_server.h"
 #  include "opentelemetry/logs/provider.h"
 #  include "opentelemetry/sdk/logs/exporter.h"
-#  include "opentelemetry/sdk/logs/log_record.h"
 #  include "opentelemetry/sdk/logs/logger_provider.h"
 #  include "opentelemetry/sdk/logs/simple_log_record_processor.h"
 
diff --git a/exporters/etw/include/opentelemetry/exporters/etw/etw_logger.h b/exporters/etw/include/opentelemetry/exporters/etw/etw_logger.h
index 5897d53808..d72fe64edb 100644
--- a/exporters/etw/include/opentelemetry/exporters/etw/etw_logger.h
+++ b/exporters/etw/include/opentelemetry/exporters/etw/etw_logger.h
@@ -6,6 +6,7 @@
 
 #  include <algorithm>
 
+#  include <chrono>
 #  include <cstdint>
 #  include <cstdio>
 #  include <cstdlib>
@@ -15,6 +16,7 @@
 #  include <fstream>
 
 #  include <map>
+#  include <unordered_map>
 
 #  include "opentelemetry/nostd/shared_ptr.h"
 #  include "opentelemetry/nostd/string_view.h"
@@ -23,6 +25,7 @@
 
 #  include "opentelemetry/common/key_value_iterable_view.h"
 
+#  include "opentelemetry/logs/log_record.h"
 #  include "opentelemetry/logs/logger_provider.h"
 #  include "opentelemetry/trace/span_id.h"
 #  include "opentelemetry/trace/trace_id.h"
@@ -41,6 +44,88 @@ namespace etw
 
 class LoggerProvider;
 
+class LogRecord : public opentelemetry::logs::LogRecord
+{
+public:
+  ~LogRecord() override = default;
+
+  void SetTimestamp(opentelemetry::common::SystemTimestamp timestamp) noexcept override
+  {
+    timestamp_ = timestamp;
+  }
+
+  opentelemetry::common::SystemTimestamp GetTimestamp() const noexcept { return timestamp_; }
+
+  void SetObservedTimestamp(opentelemetry::common::SystemTimestamp timestamp) noexcept override
+  {
+    observed_timestamp_ = timestamp;
+  }
+
+  opentelemetry::common::SystemTimestamp GetObservedTimestamp() const noexcept
+  {
+    return observed_timestamp_;
+  }
+
+  void SetSeverity(opentelemetry::logs::Severity severity) noexcept override
+  {
+    severity_ = severity;
+  }
+
+  opentelemetry::logs::Severity GetSeverity() const noexcept { return severity_; }
+
+  void SetBody(const opentelemetry::common::AttributeValue &message) noexcept override
+  {
+    body_ = message;
+  }
+
+  const opentelemetry::common::AttributeValue &GetBody() const noexcept { return body_; }
+
+  void SetAttribute(nostd::string_view key,
+                    const opentelemetry::common::AttributeValue &value) noexcept override
+  {
+    attributes_map_[static_cast<std::string>(key)] = value;
+  }
+
+  const std::unordered_map<std::string, opentelemetry::common::AttributeValue> &GetAttributes()
+      const noexcept
+  {
+    return attributes_map_;
+  }
+
+  void SetTraceId(const opentelemetry::trace::TraceId &trace_id) noexcept override
+  {
+    trace_id_ = trace_id;
+  }
+
+  const opentelemetry::trace::TraceId &GetTraceId() const noexcept { return trace_id_; }
+
+  void SetSpanId(const opentelemetry::trace::SpanId &span_id) noexcept override
+  {
+    span_id_ = span_id;
+  }
+
+  const opentelemetry::trace::SpanId &GetSpanId() const noexcept { return span_id_; }
+
+  void SetTraceFlags(const opentelemetry::trace::TraceFlags &trace_flags) noexcept override
+  {
+    trace_flags_ = trace_flags;
+  }
+
+  const opentelemetry::trace::TraceFlags &GetTraceFlags() const noexcept { return trace_flags_; }
+
+private:
+  opentelemetry::logs::Severity severity_ = opentelemetry::logs::Severity::kInvalid;
+
+  std::unordered_map<std::string, opentelemetry::common::AttributeValue> attributes_map_;
+  opentelemetry::common::AttributeValue body_ = opentelemetry::nostd::string_view();
+  opentelemetry::common::SystemTimestamp timestamp_;
+  opentelemetry::common::SystemTimestamp observed_timestamp_ = std::chrono::system_clock::now();
+
+  opentelemetry::trace::TraceId trace_id_;
+  opentelemetry::trace::SpanId span_id_;
+  opentelemetry::trace::TraceFlags trace_flags_;
+};
+
 /**
  * @brief Logger  class that allows to send logs to ETW Provider.
  */
@@ -100,53 +185,38 @@ class Logger : public opentelemetry::logs::Logger
         provHandle(initProvHandle())
   {}
 
-  void Log(opentelemetry::logs::Severity severity,
-           nostd::string_view body,
-           const common::KeyValueIterable &attributes,
-           opentelemetry::trace::TraceId trace_id,
-           opentelemetry::trace::SpanId span_id,
-           opentelemetry::trace::TraceFlags trace_flags,
-           common::SystemTimestamp timestamp) noexcept override
+  nostd::unique_ptr<opentelemetry::logs::LogRecord> CreateLogRecord() noexcept
   {
+    return nostd::unique_ptr<opentelemetry::logs::LogRecord>(new LogRecord());
+  }
 
-#  ifdef OPENTELEMETRY_RTTI_ENABLED
-    common::KeyValueIterable &attribs = const_cast<common::KeyValueIterable &>(attributes);
-    Properties *evt                   = dynamic_cast<Properties *>(&attribs);
-    // Properties *res                   = dynamic_cast<Properties *>(&resr);
+  using opentelemetry::logs::Logger::EmitLogRecord;
 
-    if (evt != nullptr)
+  void EmitLogRecord(
+      nostd::unique_ptr<opentelemetry::logs::LogRecord> &&log_record) noexcept override
+  {
+    if (!log_record)
     {
-      // Pass as a reference to original modifyable collection without creating a copy
-      return Log(severity, provId, body, *evt, trace_id, span_id, trace_flags, timestamp);
+      return;
     }
-#  endif
-    Properties evtCopy = attributes;
-    return Log(severity, provId, body, evtCopy, trace_id, span_id, trace_flags, timestamp);
-  }
+    LogRecord *readable_record = static_cast<LogRecord *>(log_record.get());
 
-  void Log(opentelemetry::logs::Severity severity,
-           nostd::string_view name,
-           nostd::string_view body,
-           const common::KeyValueIterable &attributes,
-           opentelemetry::trace::TraceId trace_id,
-           opentelemetry::trace::SpanId span_id,
-           opentelemetry::trace::TraceFlags trace_flags,
-           common::SystemTimestamp timestamp) noexcept override
-  {
-
-#  ifdef OPENTELEMETRY_RTTI_ENABLED
-    common::KeyValueIterable &attribs = const_cast<common::KeyValueIterable &>(attributes);
-    Properties *evt                   = dynamic_cast<Properties *>(&attribs);
-    // Properties *res                   = dynamic_cast<Properties *>(&resr);
-
-    if (evt != nullptr)
+    nostd::string_view body;
+    if (nostd::holds_alternative<const char *>(readable_record->GetBody()))
     {
-      // Pass as a reference to original modifyable collection without creating a copy
-      return Log(severity, name, body, *evt, trace_id, span_id, trace_flags, timestamp);
+      body = nostd::get<const char *>(readable_record->GetBody());
     }
-#  endif
-    Properties evtCopy = attributes;
-    return Log(severity, name, body, evtCopy, trace_id, span_id, trace_flags, timestamp);
+    else if (nostd::holds_alternative<nostd::string_view>(readable_record->GetBody()))
+    {
+      body = nostd::get<nostd::string_view>(readable_record->GetBody());
+    }
+    // TODO: More body types?
+
+    Properties evtCopy{
+        opentelemetry::common::MakeKeyValueIterableView(readable_record->GetAttributes())};
+    return Log(readable_record->GetSeverity(), provId, body, evtCopy, readable_record->GetTraceId(),
+               readable_record->GetSpanId(), readable_record->GetTraceFlags(),
+               readable_record->GetTimestamp());
   }
 
   virtual void Log(opentelemetry::logs::Severity severity,
diff --git a/exporters/ostream/include/opentelemetry/exporters/ostream/common_utils.h b/exporters/ostream/include/opentelemetry/exporters/ostream/common_utils.h
index 1e51815b23..11e098c5c0 100644
--- a/exporters/ostream/include/opentelemetry/exporters/ostream/common_utils.h
+++ b/exporters/ostream/include/opentelemetry/exporters/ostream/common_utils.h
@@ -41,6 +41,22 @@ void print_value(const std::vector<T> &vec, std::ostream &sout)
   sout << ']';
 }
 
+template <typename T>
+void print_value(const nostd::span<T> &vec, std::ostream &sout)
+{
+  sout << '[';
+  size_t i  = 1;
+  size_t sz = vec.size();
+  for (auto v : vec)
+  {
+    sout << v;
+    if (i != sz)
+      sout << ',';
+    i++;
+  };
+  sout << ']';
+}
+
 // Prior to C++14, generic lambda is not available so fallback to functor.
 #if __cplusplus < 201402L
 
@@ -59,6 +75,23 @@ class OwnedAttributeValueVisitor
   std::ostream &sout_;
 };
 
+class AttributeValueVisitor
+{
+public:
+  AttributeValueVisitor(std::ostream &sout) : sout_(sout) {}
+
+  template <typename T>
+  void operator()(T &&arg)
+  {
+    print_value(arg, sout_);
+  }
+
+  void operator()(const nostd::string_view &&arg) { sout_.write(arg.data(), arg.size()); }
+
+private:
+  std::ostream &sout_;
+};
+
 #endif
 
 inline void print_value(const opentelemetry::sdk::common::OwnedAttributeValue &value,
@@ -76,6 +109,20 @@ inline void print_value(const opentelemetry::sdk::common::OwnedAttributeValue &v
 #endif
 }
 
+inline void print_value(const opentelemetry::common::AttributeValue &value, std::ostream &sout)
+{
+#if __cplusplus < 201402L
+  opentelemetry::nostd::visit(AttributeValueVisitor(sout), value);
+#else
+  opentelemetry::nostd::visit(
+      [&sout](auto &&arg) {
+        /* explicit this is needed by some gcc versions (observed with v5.4.0)*/
+        print_value(arg, sout);
+      },
+      value);
+#endif
+}
+
 }  // namespace ostream_common
 }  // namespace exporter
 OPENTELEMETRY_END_NAMESPACE
diff --git a/exporters/ostream/include/opentelemetry/exporters/ostream/log_record_exporter.h b/exporters/ostream/include/opentelemetry/exporters/ostream/log_record_exporter.h
index 7aa60a8409..f5e400946f 100644
--- a/exporters/ostream/include/opentelemetry/exporters/ostream/log_record_exporter.h
+++ b/exporters/ostream/include/opentelemetry/exporters/ostream/log_record_exporter.h
@@ -7,7 +7,7 @@
 #  include "opentelemetry/common/spin_lock_mutex.h"
 #  include "opentelemetry/nostd/type_traits.h"
 #  include "opentelemetry/sdk/logs/exporter.h"
-#  include "opentelemetry/sdk/logs/log_record.h"
+
 #  include "opentelemetry/version.h"
 
 #  include <iostream>
@@ -55,6 +55,9 @@ class OStreamLogRecordExporter final : public opentelemetry::sdk::logs::LogRecor
   void printAttributes(
       const std::unordered_map<std::string, opentelemetry::sdk::common::OwnedAttributeValue> &map,
       const std::string prefix = "\n\t");
+  void printAttributes(
+      const std::unordered_map<std::string, opentelemetry::common::AttributeValue> &map,
+      const std::string prefix = "\n\t");
 };
 }  // namespace logs
 }  // namespace exporter
diff --git a/exporters/ostream/src/log_record_exporter.cc b/exporters/ostream/src/log_record_exporter.cc
index 66cff9c41b..ca751b810e 100644
--- a/exporters/ostream/src/log_record_exporter.cc
+++ b/exporters/ostream/src/log_record_exporter.cc
@@ -3,11 +3,12 @@
 
 #ifdef ENABLE_LOGS_PREVIEW
 #  include "opentelemetry/exporters/ostream/log_record_exporter.h"
-#  include <mutex>
 #  include "opentelemetry/exporters/ostream/common_utils.h"
+#  include "opentelemetry/sdk/logs/read_write_log_record.h"
 #  include "opentelemetry/sdk_config.h"
 
 #  include <iostream>
+#  include <mutex>
 #  include <type_traits>
 
 namespace nostd     = opentelemetry::nostd;
@@ -27,7 +28,7 @@ OStreamLogRecordExporter::OStreamLogRecordExporter(std::ostream &sout) noexcept
 
 std::unique_ptr<sdklogs::Recordable> OStreamLogRecordExporter::MakeRecordable() noexcept
 {
-  return std::unique_ptr<sdklogs::Recordable>(new sdklogs::LogRecord());
+  return std::unique_ptr<sdklogs::Recordable>(new sdklogs::ReadWriteLogRecord());
 }
 
 sdk::common::ExportResult OStreamLogRecordExporter::Export(
@@ -42,9 +43,8 @@ sdk::common::ExportResult OStreamLogRecordExporter::Export(
 
   for (auto &record : records)
   {
-    // Convert recordable to a LogRecord so that the getters of the LogRecord can be used
-    auto log_record =
-        std::unique_ptr<sdklogs::LogRecord>(static_cast<sdklogs::LogRecord *>(record.release()));
+    auto log_record = std::unique_ptr<sdklogs::ReadWriteLogRecord>(
+        static_cast<sdklogs::ReadWriteLogRecord *>(record.release()));
 
     if (log_record == nullptr)
     {
@@ -68,9 +68,13 @@ sdk::common::ExportResult OStreamLogRecordExporter::Export(
     // Print out each field of the log record, noting that severity is separated
     // into severity_num and severity_text
     sout_ << "{\n"
-          << "  timestamp     : " << log_record->GetTimestamp().time_since_epoch().count() << "\n"
-          << "  severity_num  : " << static_cast<std::uint32_t>(log_record->GetSeverity()) << "\n"
-          << "  severity_text : ";
+          << "  timestamp          : " << log_record->GetTimestamp().time_since_epoch().count()
+          << "\n"
+          << "  observed_timestamp : "
+          << log_record->GetObservedTimestamp().time_since_epoch().count() << "\n"
+          << "  severity_num       : " << static_cast<std::uint32_t>(log_record->GetSeverity())
+          << "\n"
+          << "  severity_text      : ";
 
     std::uint32_t severity_index = static_cast<std::uint32_t>(log_record->GetSeverity());
     if (severity_index >= std::extent<decltype(opentelemetry::logs::SeverityNumToText)>::value)
@@ -82,20 +86,22 @@ sdk::common::ExportResult OStreamLogRecordExporter::Export(
       sout_ << opentelemetry::logs::SeverityNumToText[severity_index] << "\n";
     }
 
-    sout_ << "  body          : " << log_record->GetBody() << "\n"
-          << "  resource      : ";
+    sout_ << "  body               : ";
+    opentelemetry::exporter::ostream_common::print_value(log_record->GetBody(), sout_);
+    sout_ << "\n";
 
+    sout_ << "  resource           : ";
     printAttributes(log_record->GetResource().GetAttributes());
 
     sout_ << "\n"
-          << "  attributes    : ";
+          << "  attributes         : ";
 
     printAttributes(log_record->GetAttributes());
 
     sout_ << "\n"
-          << "  trace_id      : " << std::string(trace_id, trace_id_len) << "\n"
-          << "  span_id       : " << std::string(span_id, span_id__len) << "\n"
-          << "  trace_flags   : " << std::string(trace_flags, trace_flags_len) << "\n"
+          << "  trace_id           : " << std::string(trace_id, trace_id_len) << "\n"
+          << "  span_id            : " << std::string(span_id, span_id__len) << "\n"
+          << "  trace_flags        : " << std::string(trace_flags, trace_flags_len) << "\n"
           << "}\n";
   }
 
@@ -126,6 +132,17 @@ void OStreamLogRecordExporter::printAttributes(
   }
 }
 
+void OStreamLogRecordExporter::printAttributes(
+    const std::unordered_map<std::string, opentelemetry::common::AttributeValue> &map,
+    const std::string prefix)
+{
+  for (const auto &kv : map)
+  {
+    sout_ << prefix << kv.first << ": ";
+    opentelemetry::exporter::ostream_common::print_value(kv.second, sout_);
+  }
+}
+
 }  // namespace logs
 }  // namespace exporter
 OPENTELEMETRY_END_NAMESPACE
diff --git a/exporters/ostream/test/ostream_log_test.cc b/exporters/ostream/test/ostream_log_test.cc
index 12551caf83..e247f06038 100644
--- a/exporters/ostream/test/ostream_log_test.cc
+++ b/exporters/ostream/test/ostream_log_test.cc
@@ -8,6 +8,7 @@
 #  include "opentelemetry/logs/provider.h"
 #  include "opentelemetry/nostd/span.h"
 #  include "opentelemetry/sdk/logs/logger_provider.h"
+#  include "opentelemetry/sdk/logs/read_write_log_record.h"
 #  include "opentelemetry/sdk/logs/simple_log_record_processor.h"
 
 #  include <gtest/gtest.h>
@@ -42,7 +43,7 @@ TEST(OStreamLogRecordExporter, Shutdown)
 
   // After processor/exporter is shutdown, no logs should be sent to stream
   auto record = exporter->MakeRecordable();
-  record->SetBody("Log record not empty");
+  static_cast<sdklogs::ReadWriteLogRecord *>(record.get())->SetBody("Log record not empty");
   exporter->Export(nostd::span<std::unique_ptr<sdklogs::Recordable>>(&record, 1));
 
   // Restore original stringstream buffer
@@ -77,18 +78,18 @@ TEST(OstreamLogExporter, DefaultLogRecordToCout)
 
   std::vector<std::string> expected_output{
       "{\n"
-      "  timestamp     : 0\n"
-      "  severity_num  : 0\n"
-      "  severity_text : INVALID\n"
-      "  body          : \n",
-      "  resource      : \n",
+      "  timestamp          : 0\n",
+      "  severity_num       : 0\n"
+      "  severity_text      : INVALID\n"
+      "  body               : \n",
+      "  resource           : \n",
       "telemetry.sdk.version: " OPENTELEMETRY_VERSION "\n",
       "telemetry.sdk.name: opentelemetry\n",
       "telemetry.sdk.language: cpp\n",
-      "  attributes    : \n"
-      "  trace_id      : 00000000000000000000000000000000\n"
-      "  span_id       : 0000000000000000\n"
-      "  trace_flags   : 00\n"
+      "  attributes         : \n"
+      "  trace_id           : 00000000000000000000000000000000\n"
+      "  span_id            : 0000000000000000\n"
+      "  trace_flags        : 00\n"
       "}\n"};
 
   for (auto &expected : expected_output)
@@ -114,10 +115,12 @@ TEST(OStreamLogRecordExporter, SimpleLogToCout)
   // Create a log record and manually timestamp, severity, name, message
   common::SystemTimestamp now(std::chrono::system_clock::now());
 
-  auto record = std::unique_ptr<sdklogs::Recordable>(new sdklogs::LogRecord());
-  record->SetTimestamp(now);
-  record->SetSeverity(logs_api::Severity::kTrace);  // kTrace has enum value of 1
-  record->SetBody("Message");
+  auto record = std::unique_ptr<sdklogs::Recordable>(new sdklogs::ReadWriteLogRecord());
+  static_cast<sdklogs::ReadWriteLogRecord *>(record.get())->SetTimestamp(now);
+  static_cast<sdklogs::ReadWriteLogRecord *>(record.get())->SetObservedTimestamp(now);
+  static_cast<sdklogs::ReadWriteLogRecord *>(record.get())
+      ->SetSeverity(logs_api::Severity::kTrace);  // kTrace has enum value of 1
+  static_cast<sdklogs::ReadWriteLogRecord *>(record.get())->SetBody("Message");
 
   // Log a record to cout
   exporter->Export(nostd::span<std::unique_ptr<sdklogs::Recordable>>(&record, 1));
@@ -127,20 +130,23 @@ TEST(OStreamLogRecordExporter, SimpleLogToCout)
 
   std::vector<std::string> expected_output{
       "{\n"
-      "  timestamp     : " +
+      "  timestamp          : " +
           std::to_string(now.time_since_epoch().count()) +
           "\n"
-          "  severity_num  : 1\n"
-          "  severity_text : TRACE\n"
-          "  body          : Message\n",
-      "  resource      : \n",
+          "  observed_timestamp : " +
+          std::to_string(now.time_since_epoch().count()) +
+          "\n"
+          "  severity_num       : 1\n"
+          "  severity_text      : TRACE\n"
+          "  body               : Message\n",
+      "  resource           : \n",
       "telemetry.sdk.version: " OPENTELEMETRY_VERSION "\n",
       "telemetry.sdk.name: opentelemetry\n",
       "telemetry.sdk.language: cpp\n",
-      "  attributes    : \n"
-      "  trace_id      : 00000000000000000000000000000000\n"
-      "  span_id       : 0000000000000000\n"
-      "  trace_flags   : 00\n"
+      "  attributes         : \n"
+      "  trace_id           : 00000000000000000000000000000000\n"
+      "  span_id            : 0000000000000000\n"
+      "  trace_flags        : 00\n"
       "}\n"};
 
   for (auto &expected : expected_output)
@@ -169,10 +175,10 @@ TEST(OStreamLogRecordExporter, LogWithStringAttributesToCerr)
 
   // Set resources for this log record only of type <string, string>
   auto resource = opentelemetry::sdk::resource::Resource::Create({{"key1", "val1"}});
-  record->SetResource(resource);
+  static_cast<sdklogs::ReadWriteLogRecord *>(record.get())->SetResource(resource);
 
   // Set attributes to this log record of type <string, AttributeValue>
-  record->SetAttribute("a", true);
+  static_cast<sdklogs::ReadWriteLogRecord *>(record.get())->SetAttribute("a", true);
 
   // Log record to cerr
   exporter->Export(nostd::span<std::unique_ptr<sdklogs::Recordable>>(&record, 1));
@@ -182,21 +188,21 @@ TEST(OStreamLogRecordExporter, LogWithStringAttributesToCerr)
 
   std::vector<std::string> expected_output{
       "{\n"
-      "  timestamp     : 0\n"
-      "  severity_num  : 0\n"
-      "  severity_text : INVALID\n"
-      "  body          : \n",
-      "  resource      : \n",
+      "  timestamp          : 0\n",
+      "  severity_num       : 0\n"
+      "  severity_text      : INVALID\n"
+      "  body               : \n",
+      "  resource           : \n",
       "telemetry.sdk.version: " OPENTELEMETRY_VERSION "\n",
       "telemetry.sdk.name: opentelemetry\n",
       "telemetry.sdk.language: cpp\n",
       "service.name: unknown_service\n",
       "key1: val1\n",
-      "  attributes    : \n",
+      "  attributes         : \n",
       "\ta: 1\n",
-      "  trace_id      : 00000000000000000000000000000000\n"
-      "  span_id       : 0000000000000000\n"
-      "  trace_flags   : 00\n"
+      "  trace_id           : 00000000000000000000000000000000\n"
+      "  span_id            : 0000000000000000\n"
+      "  trace_flags        : 00\n"
       "}\n"};
 
   for (auto &expected : expected_output)
@@ -229,12 +235,13 @@ TEST(OStreamLogRecordExporter, LogWithVariantTypesToClog)
   nostd::span<int> data1{array1.data(), array1.size()};
 
   auto resource = opentelemetry::sdk::resource::Resource::Create({{"res1", data1}});
-  record->SetResource(resource);
+  static_cast<sdklogs::ReadWriteLogRecord *>(record.get())->SetResource(resource);
 
   // Set resources for this log record of bool types as the value
   // e.g. key/value is a par of type <string, array of bools>
   std::array<bool, 3> array = {false, true, false};
-  record->SetAttribute("attr1", nostd::span<bool>{array.data(), array.size()});
+  static_cast<sdklogs::ReadWriteLogRecord *>(record.get())
+      ->SetAttribute("attr1", nostd::span<bool>{array.data(), array.size()});
 
   // Log a record to clog
   exporter->Export(nostd::span<std::unique_ptr<sdklogs::Recordable>>(&record, 1));
@@ -244,21 +251,21 @@ TEST(OStreamLogRecordExporter, LogWithVariantTypesToClog)
 
   std::vector<std::string> expected_output{
       "{\n"
-      "  timestamp     : 0\n"
-      "  severity_num  : 0\n"
-      "  severity_text : INVALID\n"
-      "  body          : \n",
-      "  resource      : \n",
+      "  timestamp          : 0\n",
+      "  severity_num       : 0\n"
+      "  severity_text      : INVALID\n"
+      "  body               : \n",
+      "  resource           : \n",
       "service.name: unknown_service\n",
       "telemetry.sdk.version: " OPENTELEMETRY_VERSION "\n",
       "telemetry.sdk.name: opentelemetry\n",
       "telemetry.sdk.language: cpp\n",
       "res1: [1,2,3]\n",
-      "attributes    : \n",
+      "attributes         : \n",
       "\tattr1: [0,1,0]\n"
-      "  trace_id      : 00000000000000000000000000000000\n"
-      "  span_id       : 0000000000000000\n"
-      "  trace_flags   : 00\n"
+      "  trace_id           : 00000000000000000000000000000000\n"
+      "  span_id            : 0000000000000000\n"
+      "  trace_flags        : 00\n"
       "}\n"};
 
   for (auto &expected : expected_output)
@@ -303,21 +310,20 @@ TEST(OStreamLogRecordExporter, IntegrationTest)
   // Compare actual vs expected outputs
   std::vector<std::string> expected_output{
       "{\n"
-      "  timestamp     : " +
-          std::to_string(now.time_since_epoch().count()) +
-          "\n"
-          "  severity_num  : 5\n"
-          "  severity_text : DEBUG\n"
-          "  body          : Hello\n",
-      "  resource      : \n",
+      "  timestamp          : " +
+          std::to_string(now.time_since_epoch().count()) + "\n",
+      "  severity_num       : 5\n"
+      "  severity_text      : DEBUG\n"
+      "  body               : Hello\n",
+      "  resource           : \n",
       "telemetry.sdk.version: " OPENTELEMETRY_VERSION "\n",
       "service.name: unknown_service\n",
       "telemetry.sdk.name: opentelemetry\n",
       "telemetry.sdk.language: cpp\n",
-      "  attributes    : \n"
-      "  trace_id      : 00000000000000000000000000000000\n"
-      "  span_id       : 0000000000000000\n"
-      "  trace_flags   : 00\n"
+      "  attributes         : \n"
+      "  trace_id           : 00000000000000000000000000000000\n"
+      "  span_id            : 0000000000000000\n"
+      "  trace_flags        : 00\n"
       "}\n"};
 
   for (auto &expected : expected_output)
diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_log_recordable.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_log_recordable.h
index b10898ac3c..cef1b3ef8f 100644
--- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_log_recordable.h
+++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_log_recordable.h
@@ -16,6 +16,7 @@
 
 #  include "opentelemetry/common/macros.h"
 #  include "opentelemetry/sdk/common/attribute_utils.h"
+#  include "opentelemetry/sdk/logs/read_write_log_record.h"
 #  include "opentelemetry/sdk/logs/recordable.h"
 
 OPENTELEMETRY_BEGIN_NAMESPACE
@@ -32,11 +33,15 @@ class OtlpLogRecordable final : public opentelemetry::sdk::logs::Recordable
 public:
   ~OtlpLogRecordable() override = default;
 
-  proto::logs::v1::LogRecord &log_record() noexcept { return log_record_; }
-  const proto::logs::v1::LogRecord &log_record() const noexcept { return log_record_; }
+  proto::logs::v1::LogRecord &log_record() noexcept { return proto_record_; }
+  const proto::logs::v1::LogRecord &log_record() const noexcept { return proto_record_; }
 
-  /** Dynamically converts the resource of this log into a proto. */
-  proto::resource::v1::Resource ProtoResource() const noexcept;
+  /** Returns the associated resource */
+  const opentelemetry::sdk::resource::Resource &GetResource() const noexcept;
+
+  /** Returns the associated instruementation scope */
+  const opentelemetry::sdk::instrumentationscope::InstrumentationScope &GetInstrumentationScope()
+      const noexcept;
 
   /**
    * Set the timestamp for this log.
@@ -44,6 +49,12 @@ class OtlpLogRecordable final : public opentelemetry::sdk::logs::Recordable
    */
   void SetTimestamp(opentelemetry::common::SystemTimestamp timestamp) noexcept override;
 
+  /**
+   * Set the observed timestamp for this log.
+   * @param timestamp the timestamp to set
+   */
+  void SetObservedTimestamp(opentelemetry::common::SystemTimestamp timestamp) noexcept override;
+
   /**
    * Set the severity for this log.
    * @param severity the severity of the event
@@ -54,42 +65,39 @@ class OtlpLogRecordable final : public opentelemetry::sdk::logs::Recordable
    * Set body field for this log.
    * @param message the body to set
    */
-  void SetBody(nostd::string_view message) noexcept override;
-
-  /**
-   * Set Resource of this log
-   * @param Resource the resource to set
-   */
-  void SetResource(const opentelemetry::sdk::resource::Resource &resource) noexcept override;
-
-  /** Returns the associated resource */
-  const opentelemetry::sdk::resource::Resource &GetResource() const noexcept;
-
-  /**
-   * Set an attribute of a log.
-   * @param key the name of the attribute
-   * @param value the attribute value
-   */
-  void SetAttribute(nostd::string_view key,
-                    const opentelemetry::common::AttributeValue &value) noexcept override;
+  void SetBody(const opentelemetry::common::AttributeValue &message) noexcept override;
 
   /**
    * Set the trace id for this log.
    * @param trace_id the trace id to set
    */
-  void SetTraceId(opentelemetry::trace::TraceId trace_id) noexcept override;
+  void SetTraceId(const opentelemetry::trace::TraceId &trace_id) noexcept override;
 
   /**
    * Set the span id for this log.
    * @param span_id the span id to set
    */
-  void SetSpanId(opentelemetry::trace::SpanId span_id) noexcept override;
+  void SetSpanId(const opentelemetry::trace::SpanId &span_id) noexcept override;
 
   /**
    * Inject trace_flags for this log.
    * @param trace_flags the trace flags to set
    */
-  void SetTraceFlags(opentelemetry::trace::TraceFlags trace_flags) noexcept override;
+  void SetTraceFlags(const opentelemetry::trace::TraceFlags &trace_flags) noexcept override;
+
+  /**
+   * Set an attribute of a log.
+   * @param key the name of the attribute
+   * @param value the attribute value
+   */
+  void SetAttribute(nostd::string_view key,
+                    const opentelemetry::common::AttributeValue &value) noexcept override;
+
+  /**
+   * Set Resource of this log
+   * @param Resource the resource to set
+   */
+  void SetResource(const opentelemetry::sdk::resource::Resource &resource) noexcept override;
 
   /**
    * Set instrumentation_scope for this log.
@@ -98,22 +106,9 @@ class OtlpLogRecordable final : public opentelemetry::sdk::logs::Recordable
   void SetInstrumentationScope(const opentelemetry::sdk::instrumentationscope::InstrumentationScope
                                    &instrumentation_scope) noexcept override;
 
-  OPENTELEMETRY_DEPRECATED_MESSAGE("Please use GetInstrumentationScope instead")
-  const opentelemetry::sdk::instrumentationscope::InstrumentationScope &GetInstrumentationLibrary()
-      const noexcept
-  {
-    return GetInstrumentationScope();
-  }
-
-  /** Returns the associated instruementation scope */
-  const opentelemetry::sdk::instrumentationscope::InstrumentationScope &GetInstrumentationScope()
-      const noexcept;
-
 private:
-  proto::logs::v1::LogRecord log_record_;
+  proto::logs::v1::LogRecord proto_record_;
   const opentelemetry::sdk::resource::Resource *resource_ = nullptr;
-  // TODO shared resource
-  // const opentelemetry::sdk::resource::Resource *resource_ = nullptr;
   const opentelemetry::sdk::instrumentationscope::InstrumentationScope *instrumentation_scope_ =
       nullptr;
 };
diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_populate_attribute_utils.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_populate_attribute_utils.h
index c64ca90b43..dc43827641 100644
--- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_populate_attribute_utils.h
+++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_populate_attribute_utils.h
@@ -3,8 +3,11 @@
 
 #pragma once
 
+// clang-format off
 #include "opentelemetry/exporters/otlp/protobuf_include_prefix.h"
 #include "opentelemetry/proto/resource/v1/resource.pb.h"
+#include "opentelemetry/exporters/otlp/protobuf_include_suffix.h"
+// clang-format on
 
 #include "opentelemetry/common/attribute_value.h"
 #include "opentelemetry/nostd/string_view.h"
@@ -27,6 +30,13 @@ class OtlpPopulateAttributeUtils
   static void PopulateAttribute(opentelemetry::proto::resource::v1::Resource *proto,
                                 const opentelemetry::sdk::resource::Resource &resource) noexcept;
 
+  static void PopulateAnyValue(opentelemetry::proto::common::v1::AnyValue *proto_value,
+                               const opentelemetry::common::AttributeValue &value) noexcept;
+
+  static void PopulateAnyValue(
+      opentelemetry::proto::common::v1::AnyValue *proto_value,
+      const opentelemetry::sdk::common::OwnedAttributeValue &value) noexcept;
+
   static void PopulateAttribute(opentelemetry::proto::common::v1::KeyValue *attribute,
                                 nostd::string_view key,
                                 const opentelemetry::common::AttributeValue &value) noexcept;
diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_recordable_utils.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_recordable_utils.h
index 57dbd3b8b0..1ad9514aa9 100644
--- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_recordable_utils.h
+++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_recordable_utils.h
@@ -3,6 +3,10 @@
 
 #pragma once
 
+#include <memory>
+
+#include "opentelemetry/nostd/unique_ptr.h"
+
 #include "opentelemetry/exporters/otlp/protobuf_include_prefix.h"
 
 #include "opentelemetry/proto/collector/logs/v1/logs_service.pb.h"
@@ -16,8 +20,6 @@
 #  include "opentelemetry/sdk/logs/recordable.h"
 #endif
 
-#include <memory>
-
 OPENTELEMETRY_BEGIN_NAMESPACE
 namespace exporter
 {
diff --git a/exporters/otlp/src/otlp_grpc_log_record_exporter.cc b/exporters/otlp/src/otlp_grpc_log_record_exporter.cc
index 75e6b36597..e8a3b8a46a 100644
--- a/exporters/otlp/src/otlp_grpc_log_record_exporter.cc
+++ b/exporters/otlp/src/otlp_grpc_log_record_exporter.cc
@@ -51,8 +51,7 @@ OtlpGrpcLogRecordExporter::OtlpGrpcLogRecordExporter(
 std::unique_ptr<opentelemetry::sdk::logs::Recordable>
 OtlpGrpcLogRecordExporter::MakeRecordable() noexcept
 {
-  return std::unique_ptr<opentelemetry::sdk::logs::Recordable>(
-      new exporter::otlp::OtlpLogRecordable());
+  return std::unique_ptr<opentelemetry::sdk::logs::Recordable>(new OtlpLogRecordable());
 }
 
 opentelemetry::sdk::common::ExportResult OtlpGrpcLogRecordExporter::Export(
diff --git a/exporters/otlp/src/otlp_http_log_record_exporter.cc b/exporters/otlp/src/otlp_http_log_record_exporter.cc
index 04bd05d063..81bc37a340 100644
--- a/exporters/otlp/src/otlp_http_log_record_exporter.cc
+++ b/exporters/otlp/src/otlp_http_log_record_exporter.cc
@@ -67,8 +67,7 @@ OtlpHttpLogRecordExporter::OtlpHttpLogRecordExporter(std::unique_ptr<OtlpHttpCli
 std::unique_ptr<opentelemetry::sdk::logs::Recordable>
 OtlpHttpLogRecordExporter::MakeRecordable() noexcept
 {
-  return std::unique_ptr<opentelemetry::sdk::logs::Recordable>(
-      new exporter::otlp::OtlpLogRecordable());
+  return std::unique_ptr<opentelemetry::sdk::logs::Recordable>(new OtlpLogRecordable());
 }
 
 opentelemetry::sdk::common::ExportResult OtlpHttpLogRecordExporter::Export(
diff --git a/exporters/otlp/src/otlp_log_recordable.cc b/exporters/otlp/src/otlp_log_recordable.cc
index 568bfa1a0a..cc4d712795 100644
--- a/exporters/otlp/src/otlp_log_recordable.cc
+++ b/exporters/otlp/src/otlp_log_recordable.cc
@@ -6,9 +6,9 @@
 #  include "opentelemetry/common/macros.h"
 
 #  include "opentelemetry/exporters/otlp/otlp_log_recordable.h"
-
 #  include "opentelemetry/exporters/otlp/otlp_populate_attribute_utils.h"
 #  include "opentelemetry/exporters/otlp/otlp_recordable_utils.h"
+#  include "opentelemetry/sdk/logs/readable_log_record.h"
 
 namespace nostd = opentelemetry::nostd;
 
@@ -18,24 +18,30 @@ namespace exporter
 namespace otlp
 {
 
-proto::resource::v1::Resource OtlpLogRecordable::ProtoResource() const noexcept
+const opentelemetry::sdk::resource::Resource &OtlpLogRecordable::GetResource() const noexcept
 {
-  proto::resource::v1::Resource proto;
-  if (nullptr == resource_)
-  {
-    OtlpPopulateAttributeUtils::PopulateAttribute(&proto, sdk::resource::Resource::GetDefault());
-  }
-  else
-  {
-    OtlpPopulateAttributeUtils::PopulateAttribute(&proto, *resource_);
-  }
+  OPENTELEMETRY_LIKELY_IF(nullptr != resource_) { return *resource_; }
+
+  return opentelemetry::sdk::logs::ReadableLogRecord::GetDefaultResource();
+}
+
+const opentelemetry::sdk::instrumentationscope::InstrumentationScope &
+OtlpLogRecordable::GetInstrumentationScope() const noexcept
+{
+  OPENTELEMETRY_LIKELY_IF(nullptr != instrumentation_scope_) { return *instrumentation_scope_; }
 
-  return proto;
+  return opentelemetry::sdk::logs::ReadableLogRecord::GetDefaultInstrumentationScope();
 }
 
 void OtlpLogRecordable::SetTimestamp(opentelemetry::common::SystemTimestamp timestamp) noexcept
 {
-  log_record_.set_time_unix_nano(timestamp.time_since_epoch().count());
+  proto_record_.set_time_unix_nano(timestamp.time_since_epoch().count());
+}
+
+void OtlpLogRecordable::SetObservedTimestamp(
+    opentelemetry::common::SystemTimestamp timestamp) noexcept
+{
+  proto_record_.set_observed_time_unix_nano(timestamp.time_since_epoch().count());
 }
 
 void OtlpLogRecordable::SetSeverity(opentelemetry::logs::Severity severity) noexcept
@@ -43,171 +49,178 @@ void OtlpLogRecordable::SetSeverity(opentelemetry::logs::Severity severity) noex
   switch (severity)
   {
     case opentelemetry::logs::Severity::kTrace: {
-      log_record_.set_severity_text("TRACE");
-      log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_TRACE);
+      proto_record_.set_severity_text("TRACE");
+      proto_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_TRACE);
       break;
     }
     case opentelemetry::logs::Severity::kTrace2: {
-      log_record_.set_severity_text("TRACE2");
-      log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_TRACE2);
+      proto_record_.set_severity_text("TRACE2");
+      proto_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_TRACE2);
       break;
     }
     case opentelemetry::logs::Severity::kTrace3: {
-      log_record_.set_severity_text("TRACE3");
-      log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_TRACE3);
+      proto_record_.set_severity_text("TRACE3");
+      proto_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_TRACE3);
       break;
     }
     case opentelemetry::logs::Severity::kTrace4: {
-      log_record_.set_severity_text("TRACE4");
-      log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_TRACE4);
+      proto_record_.set_severity_text("TRACE4");
+      proto_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_TRACE4);
       break;
     }
     case opentelemetry::logs::Severity::kDebug: {
-      log_record_.set_severity_text("DEBUG");
-      log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_DEBUG);
+      proto_record_.set_severity_text("DEBUG");
+      proto_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_DEBUG);
       break;
     }
     case opentelemetry::logs::Severity::kDebug2: {
-      log_record_.set_severity_text("DEBUG2");
-      log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_DEBUG2);
+      proto_record_.set_severity_text("DEBUG2");
+      proto_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_DEBUG2);
       break;
     }
     case opentelemetry::logs::Severity::kDebug3: {
-      log_record_.set_severity_text("DEBUG3");
-      log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_DEBUG3);
+      proto_record_.set_severity_text("DEBUG3");
+      proto_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_DEBUG3);
       break;
     }
     case opentelemetry::logs::Severity::kDebug4: {
-      log_record_.set_severity_text("DEBUG4");
-      log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_DEBUG4);
+      proto_record_.set_severity_text("DEBUG4");
+      proto_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_DEBUG4);
       break;
     }
     case opentelemetry::logs::Severity::kInfo: {
-      log_record_.set_severity_text("INFO");
-      log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_INFO);
+      proto_record_.set_severity_text("INFO");
+      proto_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_INFO);
       break;
     }
     case opentelemetry::logs::Severity::kInfo2: {
-      log_record_.set_severity_text("INFO2");
-      log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_INFO2);
+      proto_record_.set_severity_text("INFO2");
+      proto_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_INFO2);
       break;
     }
     case opentelemetry::logs::Severity::kInfo3: {
-      log_record_.set_severity_text("INFO3");
-      log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_INFO3);
+      proto_record_.set_severity_text("INFO3");
+      proto_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_INFO3);
       break;
     }
     case opentelemetry::logs::Severity::kInfo4: {
-      log_record_.set_severity_text("INFO4");
-      log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_INFO4);
+      proto_record_.set_severity_text("INFO4");
+      proto_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_INFO4);
       break;
     }
     case opentelemetry::logs::Severity::kWarn: {
-      log_record_.set_severity_text("WARN");
-      log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_WARN);
+      proto_record_.set_severity_text("WARN");
+      proto_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_WARN);
       break;
     }
     case opentelemetry::logs::Severity::kWarn2: {
-      log_record_.set_severity_text("WARN2");
-      log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_WARN2);
+      proto_record_.set_severity_text("WARN2");
+      proto_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_WARN2);
       break;
     }
     case opentelemetry::logs::Severity::kWarn3: {
-      log_record_.set_severity_text("WARN3");
-      log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_WARN3);
+      proto_record_.set_severity_text("WARN3");
+      proto_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_WARN3);
       break;
     }
     case opentelemetry::logs::Severity::kWarn4: {
-      log_record_.set_severity_text("WARN4");
-      log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_WARN4);
+      proto_record_.set_severity_text("WARN4");
+      proto_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_WARN4);
       break;
     }
     case opentelemetry::logs::Severity::kError: {
-      log_record_.set_severity_text("ERROR");
-      log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_ERROR);
+      proto_record_.set_severity_text("ERROR");
+      proto_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_ERROR);
       break;
     }
     case opentelemetry::logs::Severity::kError2: {
-      log_record_.set_severity_text("ERROR2");
-      log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_ERROR2);
+      proto_record_.set_severity_text("ERROR2");
+      proto_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_ERROR2);
       break;
     }
     case opentelemetry::logs::Severity::kError3: {
-      log_record_.set_severity_text("ERROR3");
-      log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_ERROR3);
+      proto_record_.set_severity_text("ERROR3");
+      proto_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_ERROR3);
       break;
     }
     case opentelemetry::logs::Severity::kError4: {
-      log_record_.set_severity_text("ERROR4");
-      log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_ERROR4);
+      proto_record_.set_severity_text("ERROR4");
+      proto_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_ERROR4);
       break;
     }
     case opentelemetry::logs::Severity::kFatal: {
-      log_record_.set_severity_text("FATAL");
-      log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_FATAL);
+      proto_record_.set_severity_text("FATAL");
+      proto_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_FATAL);
       break;
     }
     case opentelemetry::logs::Severity::kFatal2: {
-      log_record_.set_severity_text("FATAL2");
-      log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_FATAL2);
+      proto_record_.set_severity_text("FATAL2");
+      proto_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_FATAL2);
       break;
     }
     case opentelemetry::logs::Severity::kFatal3: {
-      log_record_.set_severity_text("FATAL3");
-      log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_FATAL3);
+      proto_record_.set_severity_text("FATAL3");
+      proto_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_FATAL3);
       break;
     }
     case opentelemetry::logs::Severity::kFatal4: {
-      log_record_.set_severity_text("FATAL4");
-      log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_FATAL4);
+      proto_record_.set_severity_text("FATAL4");
+      proto_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_FATAL4);
       break;
     }
     default: {
-      log_record_.set_severity_text("INVALID");
-      log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_UNSPECIFIED);
+      proto_record_.set_severity_text("INVALID");
+      proto_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_UNSPECIFIED);
       break;
     }
   }
 }
 
-void OtlpLogRecordable::SetBody(nostd::string_view message) noexcept
+void OtlpLogRecordable::SetBody(const opentelemetry::common::AttributeValue &message) noexcept
 {
-  log_record_.mutable_body()->set_string_value(message.data(), message.size());
+  OtlpPopulateAttributeUtils::PopulateAnyValue(proto_record_.mutable_body(), message);
 }
 
-void OtlpLogRecordable::SetResource(const opentelemetry::sdk::resource::Resource &resource) noexcept
-{
-  resource_ = &resource;
-}
-
-const opentelemetry::sdk::resource::Resource &OtlpLogRecordable::GetResource() const noexcept
+void OtlpLogRecordable::SetTraceId(const opentelemetry::trace::TraceId &trace_id) noexcept
 {
-  OPENTELEMETRY_LIKELY_IF(nullptr != resource_) { return *resource_; }
-
-  return opentelemetry::sdk::resource::Resource::GetDefault();
+  if (trace_id.IsValid())
+  {
+    proto_record_.set_trace_id(reinterpret_cast<const char *>(trace_id.Id().data()),
+                               trace_id.Id().size());
+  }
+  else
+  {
+    proto_record_.clear_trace_id();
+  }
 }
 
-void OtlpLogRecordable::SetAttribute(nostd::string_view key,
-                                     const opentelemetry::common::AttributeValue &value) noexcept
+void OtlpLogRecordable::SetSpanId(const opentelemetry::trace::SpanId &span_id) noexcept
 {
-  OtlpPopulateAttributeUtils::PopulateAttribute(log_record_.add_attributes(), key, value);
+  if (span_id.IsValid())
+  {
+    proto_record_.set_span_id(reinterpret_cast<const char *>(span_id.Id().data()),
+                              span_id.Id().size());
+  }
+  else
+  {
+    proto_record_.clear_span_id();
+  }
 }
 
-void OtlpLogRecordable::SetTraceId(opentelemetry::trace::TraceId trace_id) noexcept
+void OtlpLogRecordable::SetTraceFlags(const opentelemetry::trace::TraceFlags &trace_flags) noexcept
 {
-  log_record_.set_trace_id(reinterpret_cast<const char *>(trace_id.Id().data()),
-                           trace::TraceId::kSize);
+  proto_record_.set_flags(trace_flags.flags());
 }
 
-void OtlpLogRecordable::SetSpanId(opentelemetry::trace::SpanId span_id) noexcept
+void OtlpLogRecordable::SetAttribute(nostd::string_view key,
+                                     const opentelemetry::common::AttributeValue &value) noexcept
 {
-  log_record_.set_span_id(reinterpret_cast<const char *>(span_id.Id().data()),
-                          trace::SpanId::kSize);
+  OtlpPopulateAttributeUtils::PopulateAttribute(proto_record_.add_attributes(), key, value);
 }
 
-void OtlpLogRecordable::SetTraceFlags(opentelemetry::trace::TraceFlags trace_flags) noexcept
+void OtlpLogRecordable::SetResource(const opentelemetry::sdk::resource::Resource &resource) noexcept
 {
-  log_record_.set_flags(trace_flags.flags());
+  resource_ = &resource;
 }
 
 void OtlpLogRecordable::SetInstrumentationScope(
@@ -217,18 +230,6 @@ void OtlpLogRecordable::SetInstrumentationScope(
   instrumentation_scope_ = &instrumentation_scope;
 }
 
-const opentelemetry::sdk::instrumentationscope::InstrumentationScope &
-OtlpLogRecordable::GetInstrumentationScope() const noexcept
-{
-  OPENTELEMETRY_LIKELY_IF(nullptr != instrumentation_scope_) { return *instrumentation_scope_; }
-
-  static opentelemetry::nostd::unique_ptr<
-      opentelemetry::sdk::instrumentationscope::InstrumentationScope>
-      default_instrumentation =
-          opentelemetry::sdk::instrumentationscope::InstrumentationScope::Create(
-              "default", "1.0.0", "https://opentelemetry.io/schemas/1.11.0");
-  return *default_instrumentation;
-}
 }  // namespace otlp
 }  // namespace exporter
 OPENTELEMETRY_END_NAMESPACE
diff --git a/exporters/otlp/src/otlp_populate_attribute_utils.cc b/exporters/otlp/src/otlp_populate_attribute_utils.cc
index 6dff7c4d01..d86953506c 100644
--- a/exporters/otlp/src/otlp_populate_attribute_utils.cc
+++ b/exporters/otlp/src/otlp_populate_attribute_utils.cc
@@ -17,12 +17,11 @@ namespace otlp
 const int kAttributeValueSize      = 16;
 const int kOwnedAttributeValueSize = 15;
 
-void OtlpPopulateAttributeUtils::PopulateAttribute(
-    opentelemetry::proto::common::v1::KeyValue *attribute,
-    nostd::string_view key,
+void OtlpPopulateAttributeUtils::PopulateAnyValue(
+    opentelemetry::proto::common::v1::AnyValue *proto_value,
     const opentelemetry::common::AttributeValue &value) noexcept
 {
-  if (nullptr == attribute)
+  if (nullptr == proto_value)
   {
     return;
   }
@@ -33,107 +32,102 @@ void OtlpPopulateAttributeUtils::PopulateAttribute(
       nostd::variant_size<opentelemetry::common::AttributeValue>::value == kAttributeValueSize,
       "AttributeValue contains unknown type");
 
-  attribute->set_key(key.data(), key.size());
-
   if (nostd::holds_alternative<bool>(value))
   {
-    attribute->mutable_value()->set_bool_value(nostd::get<bool>(value));
+    proto_value->set_bool_value(nostd::get<bool>(value));
   }
   else if (nostd::holds_alternative<int>(value))
   {
-    attribute->mutable_value()->set_int_value(nostd::get<int>(value));
+    proto_value->set_int_value(nostd::get<int>(value));
   }
   else if (nostd::holds_alternative<int64_t>(value))
   {
-    attribute->mutable_value()->set_int_value(nostd::get<int64_t>(value));
+    proto_value->set_int_value(nostd::get<int64_t>(value));
   }
   else if (nostd::holds_alternative<unsigned int>(value))
   {
-    attribute->mutable_value()->set_int_value(nostd::get<unsigned int>(value));
+    proto_value->set_int_value(nostd::get<unsigned int>(value));
   }
   else if (nostd::holds_alternative<uint64_t>(value))
   {
-    attribute->mutable_value()->set_int_value(nostd::get<uint64_t>(value));
+    proto_value->set_int_value(nostd::get<uint64_t>(value));
   }
   else if (nostd::holds_alternative<double>(value))
   {
-    attribute->mutable_value()->set_double_value(nostd::get<double>(value));
+    proto_value->set_double_value(nostd::get<double>(value));
   }
   else if (nostd::holds_alternative<const char *>(value))
   {
-    attribute->mutable_value()->set_string_value(nostd::get<const char *>(value));
+    proto_value->set_string_value(nostd::get<const char *>(value));
   }
   else if (nostd::holds_alternative<nostd::string_view>(value))
   {
-    attribute->mutable_value()->set_string_value(nostd::get<nostd::string_view>(value).data(),
-                                                 nostd::get<nostd::string_view>(value).size());
+    proto_value->set_string_value(nostd::get<nostd::string_view>(value).data(),
+                                  nostd::get<nostd::string_view>(value).size());
   }
   else if (nostd::holds_alternative<nostd::span<const uint8_t>>(value))
   {
     for (const auto &val : nostd::get<nostd::span<const uint8_t>>(value))
     {
-      attribute->mutable_value()->mutable_array_value()->add_values()->set_int_value(val);
+      proto_value->mutable_array_value()->add_values()->set_int_value(val);
     }
   }
   else if (nostd::holds_alternative<nostd::span<const bool>>(value))
   {
     for (const auto &val : nostd::get<nostd::span<const bool>>(value))
     {
-      attribute->mutable_value()->mutable_array_value()->add_values()->set_bool_value(val);
+      proto_value->mutable_array_value()->add_values()->set_bool_value(val);
     }
   }
   else if (nostd::holds_alternative<nostd::span<const int>>(value))
   {
     for (const auto &val : nostd::get<nostd::span<const int>>(value))
     {
-      attribute->mutable_value()->mutable_array_value()->add_values()->set_int_value(val);
+      proto_value->mutable_array_value()->add_values()->set_int_value(val);
     }
   }
   else if (nostd::holds_alternative<nostd::span<const int64_t>>(value))
   {
     for (const auto &val : nostd::get<nostd::span<const int64_t>>(value))
     {
-      attribute->mutable_value()->mutable_array_value()->add_values()->set_int_value(val);
+      proto_value->mutable_array_value()->add_values()->set_int_value(val);
     }
   }
   else if (nostd::holds_alternative<nostd::span<const unsigned int>>(value))
   {
     for (const auto &val : nostd::get<nostd::span<const unsigned int>>(value))
     {
-      attribute->mutable_value()->mutable_array_value()->add_values()->set_int_value(val);
+      proto_value->mutable_array_value()->add_values()->set_int_value(val);
     }
   }
   else if (nostd::holds_alternative<nostd::span<const uint64_t>>(value))
   {
     for (const auto &val : nostd::get<nostd::span<const uint64_t>>(value))
     {
-      attribute->mutable_value()->mutable_array_value()->add_values()->set_int_value(val);
+      proto_value->mutable_array_value()->add_values()->set_int_value(val);
     }
   }
   else if (nostd::holds_alternative<nostd::span<const double>>(value))
   {
     for (const auto &val : nostd::get<nostd::span<const double>>(value))
     {
-      attribute->mutable_value()->mutable_array_value()->add_values()->set_double_value(val);
+      proto_value->mutable_array_value()->add_values()->set_double_value(val);
     }
   }
   else if (nostd::holds_alternative<nostd::span<const nostd::string_view>>(value))
   {
     for (const auto &val : nostd::get<nostd::span<const nostd::string_view>>(value))
     {
-      attribute->mutable_value()->mutable_array_value()->add_values()->set_string_value(val.data(),
-                                                                                        val.size());
+      proto_value->mutable_array_value()->add_values()->set_string_value(val.data(), val.size());
     }
   }
 }
 
-/** Maps from C++ attribute into OTLP proto attribute. */
-void OtlpPopulateAttributeUtils::PopulateAttribute(
-    opentelemetry::proto::common::v1::KeyValue *attribute,
-    nostd::string_view key,
+void OtlpPopulateAttributeUtils::PopulateAnyValue(
+    opentelemetry::proto::common::v1::AnyValue *proto_value,
     const opentelemetry::sdk::common::OwnedAttributeValue &value) noexcept
 {
-  if (nullptr == attribute)
+  if (nullptr == proto_value)
   {
     return;
   }
@@ -144,87 +138,126 @@ void OtlpPopulateAttributeUtils::PopulateAttribute(
                     kOwnedAttributeValueSize,
                 "OwnedAttributeValue contains unknown type");
 
-  attribute->set_key(key.data(), key.size());
-
   if (nostd::holds_alternative<bool>(value))
   {
-    attribute->mutable_value()->set_bool_value(nostd::get<bool>(value));
+    proto_value->set_bool_value(nostd::get<bool>(value));
   }
   else if (nostd::holds_alternative<int32_t>(value))
   {
-    attribute->mutable_value()->set_int_value(nostd::get<int32_t>(value));
+    proto_value->set_int_value(nostd::get<int32_t>(value));
   }
   else if (nostd::holds_alternative<int64_t>(value))
   {
-    attribute->mutable_value()->set_int_value(nostd::get<int64_t>(value));
+    proto_value->set_int_value(nostd::get<int64_t>(value));
   }
   else if (nostd::holds_alternative<uint32_t>(value))
   {
-    attribute->mutable_value()->set_int_value(nostd::get<uint32_t>(value));
+    proto_value->set_int_value(nostd::get<uint32_t>(value));
   }
   else if (nostd::holds_alternative<uint64_t>(value))
   {
-    attribute->mutable_value()->set_int_value(nostd::get<uint64_t>(value));
+    proto_value->set_int_value(nostd::get<uint64_t>(value));
   }
   else if (nostd::holds_alternative<double>(value))
   {
-    attribute->mutable_value()->set_double_value(nostd::get<double>(value));
+    proto_value->set_double_value(nostd::get<double>(value));
   }
   else if (nostd::holds_alternative<std::string>(value))
   {
-    attribute->mutable_value()->set_string_value(nostd::get<std::string>(value));
+    proto_value->set_string_value(nostd::get<std::string>(value));
   }
   else if (nostd::holds_alternative<std::vector<bool>>(value))
   {
     for (const auto val : nostd::get<std::vector<bool>>(value))
     {
-      attribute->mutable_value()->mutable_array_value()->add_values()->set_bool_value(val);
+      proto_value->mutable_array_value()->add_values()->set_bool_value(val);
     }
   }
   else if (nostd::holds_alternative<std::vector<int32_t>>(value))
   {
     for (const auto &val : nostd::get<std::vector<int32_t>>(value))
     {
-      attribute->mutable_value()->mutable_array_value()->add_values()->set_int_value(val);
+      proto_value->mutable_array_value()->add_values()->set_int_value(val);
     }
   }
   else if (nostd::holds_alternative<std::vector<uint32_t>>(value))
   {
     for (const auto &val : nostd::get<std::vector<uint32_t>>(value))
     {
-      attribute->mutable_value()->mutable_array_value()->add_values()->set_int_value(val);
+      proto_value->mutable_array_value()->add_values()->set_int_value(val);
     }
   }
   else if (nostd::holds_alternative<std::vector<int64_t>>(value))
   {
     for (const auto &val : nostd::get<std::vector<int64_t>>(value))
     {
-      attribute->mutable_value()->mutable_array_value()->add_values()->set_int_value(val);
+      proto_value->mutable_array_value()->add_values()->set_int_value(val);
     }
   }
   else if (nostd::holds_alternative<std::vector<uint64_t>>(value))
   {
     for (const auto &val : nostd::get<std::vector<uint64_t>>(value))
     {
-      attribute->mutable_value()->mutable_array_value()->add_values()->set_int_value(val);
+      proto_value->mutable_array_value()->add_values()->set_int_value(val);
     }
   }
   else if (nostd::holds_alternative<std::vector<double>>(value))
   {
     for (const auto &val : nostd::get<std::vector<double>>(value))
     {
-      attribute->mutable_value()->mutable_array_value()->add_values()->set_double_value(val);
+      proto_value->mutable_array_value()->add_values()->set_double_value(val);
     }
   }
   else if (nostd::holds_alternative<std::vector<std::string>>(value))
   {
     for (const auto &val : nostd::get<std::vector<std::string>>(value))
     {
-      attribute->mutable_value()->mutable_array_value()->add_values()->set_string_value(val);
+      proto_value->mutable_array_value()->add_values()->set_string_value(val);
     }
   }
 }
 
+void OtlpPopulateAttributeUtils::PopulateAttribute(
+    opentelemetry::proto::common::v1::KeyValue *attribute,
+    nostd::string_view key,
+    const opentelemetry::common::AttributeValue &value) noexcept
+{
+  if (nullptr == attribute)
+  {
+    return;
+  }
+
+  // Assert size of variant to ensure that this method gets updated if the variant
+  // definition changes
+  static_assert(
+      nostd::variant_size<opentelemetry::common::AttributeValue>::value == kAttributeValueSize,
+      "AttributeValue contains unknown type");
+
+  attribute->set_key(key.data(), key.size());
+  PopulateAnyValue(attribute->mutable_value(), value);
+}
+
+/** Maps from C++ attribute into OTLP proto attribute. */
+void OtlpPopulateAttributeUtils::PopulateAttribute(
+    opentelemetry::proto::common::v1::KeyValue *attribute,
+    nostd::string_view key,
+    const opentelemetry::sdk::common::OwnedAttributeValue &value) noexcept
+{
+  if (nullptr == attribute)
+  {
+    return;
+  }
+
+  // Assert size of variant to ensure that this method gets updated if the variant
+  // definition changes
+  static_assert(nostd::variant_size<opentelemetry::sdk::common::OwnedAttributeValue>::value ==
+                    kOwnedAttributeValueSize,
+                "OwnedAttributeValue contains unknown type");
+
+  attribute->set_key(key.data(), key.size());
+  PopulateAnyValue(attribute->mutable_value(), value);
+}
+
 void OtlpPopulateAttributeUtils::PopulateAttribute(
     opentelemetry::proto::resource::v1::Resource *proto,
     const opentelemetry::sdk::resource::Resource &resource) noexcept
diff --git a/exporters/otlp/src/otlp_recordable_utils.cc b/exporters/otlp/src/otlp_recordable_utils.cc
index 3bfe606bab..a7f688d7f3 100644
--- a/exporters/otlp/src/otlp_recordable_utils.cc
+++ b/exporters/otlp/src/otlp_recordable_utils.cc
@@ -11,6 +11,7 @@
 #include "opentelemetry/exporters/otlp/protobuf_include_suffix.h"
 
 #include "opentelemetry/exporters/otlp/otlp_log_recordable.h"
+#include "opentelemetry/exporters/otlp/otlp_populate_attribute_utils.h"
 #include "opentelemetry/exporters/otlp/otlp_recordable.h"
 
 #include <list>
@@ -109,7 +110,8 @@ void OtlpRecordableUtils::PopulateRequest(
       {
         if (!output_resource_log->has_resource())
         {
-          *output_resource_log->mutable_resource() = input_log_record->ProtoResource();
+          OtlpPopulateAttributeUtils::PopulateAttribute(output_resource_log->mutable_resource(),
+                                                        *input_resource_log.first);
           output_resource_log->set_schema_url(input_resource_log.first->GetSchemaURL());
         }
 
diff --git a/exporters/otlp/test/otlp_grpc_log_record_exporter_test.cc b/exporters/otlp/test/otlp_grpc_log_record_exporter_test.cc
index 34aee61a0c..f87b5bf2d7 100644
--- a/exporters/otlp/test/otlp_grpc_log_record_exporter_test.cc
+++ b/exporters/otlp/test/otlp_grpc_log_record_exporter_test.cc
@@ -16,7 +16,6 @@
 #  include "opentelemetry/logs/provider.h"
 #  include "opentelemetry/sdk/logs/batch_log_record_processor.h"
 #  include "opentelemetry/sdk/logs/exporter.h"
-#  include "opentelemetry/sdk/logs/log_record.h"
 #  include "opentelemetry/sdk/logs/logger_provider.h"
 #  include "opentelemetry/sdk/resource/resource.h"
 
diff --git a/exporters/otlp/test/otlp_http_log_record_exporter_test.cc b/exporters/otlp/test/otlp_http_log_record_exporter_test.cc
index 6dc8ef8960..08bc4b1fca 100644
--- a/exporters/otlp/test/otlp_http_log_record_exporter_test.cc
+++ b/exporters/otlp/test/otlp_http_log_record_exporter_test.cc
@@ -21,7 +21,6 @@
 #    include "opentelemetry/logs/provider.h"
 #    include "opentelemetry/sdk/logs/batch_log_record_processor.h"
 #    include "opentelemetry/sdk/logs/exporter.h"
-#    include "opentelemetry/sdk/logs/log_record.h"
 #    include "opentelemetry/sdk/logs/logger_provider.h"
 #    include "opentelemetry/sdk/resource/resource.h"
 #    include "opentelemetry/test_common/ext/http/client/nosend/http_client_nosend.h"
diff --git a/exporters/otlp/test/otlp_log_recordable_test.cc b/exporters/otlp/test/otlp_log_recordable_test.cc
index a20d80b36a..96c5c66b09 100644
--- a/exporters/otlp/test/otlp_log_recordable_test.cc
+++ b/exporters/otlp/test/otlp_log_recordable_test.cc
@@ -5,7 +5,10 @@
 
 #  include <gtest/gtest.h>
 
+#  include <chrono>
+
 #  include "opentelemetry/exporters/otlp/otlp_log_recordable.h"
+#  include "opentelemetry/sdk/logs/read_write_log_record.h"
 #  include "opentelemetry/sdk/resource/resource.h"
 #  include "opentelemetry/sdk/resource/semantic_conventions.h"
 
@@ -17,7 +20,7 @@ namespace otlp
 namespace resource = opentelemetry::sdk::resource;
 namespace proto    = opentelemetry::proto;
 
-TEST(OtlpLogRecordable, SetTimestamp)
+TEST(OtlpLogRecordable, Basic)
 {
   OtlpLogRecordable rec;
   std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
@@ -27,53 +30,34 @@ TEST(OtlpLogRecordable, SetTimestamp)
       std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count();
 
   rec.SetTimestamp(start_timestamp);
-  EXPECT_EQ(rec.log_record().time_unix_nano(), unix_start);
-}
-
-TEST(OtlpLogRecordable, SetSeverity)
-{
-  OtlpLogRecordable rec;
   rec.SetSeverity(opentelemetry::logs::Severity::kError);
-
-  EXPECT_EQ(rec.log_record().severity_number(), proto::logs::v1::SEVERITY_NUMBER_ERROR);
-  EXPECT_EQ(rec.log_record().severity_text(), "ERROR");
-}
-
-TEST(OtlpLogRecordable, SetBody)
-{
-  OtlpLogRecordable rec;
   nostd::string_view name = "Test Log Message";
   rec.SetBody(name);
-  EXPECT_EQ(rec.log_record().body().string_value(), name);
-}
 
-TEST(OtlpLogRecordable, SetTraceId)
-{
-  OtlpLogRecordable rec;
   uint8_t trace_id_bin[opentelemetry::trace::TraceId::kSize] = {
       '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
-  std::string expected_bytes;
-  expected_bytes.assign(
+  std::string expected_trace_id_bytes;
+  expected_trace_id_bytes.assign(
       reinterpret_cast<char *>(trace_id_bin),
       reinterpret_cast<char *>(trace_id_bin) + opentelemetry::trace::TraceId::kSize);
   rec.SetTraceId(opentelemetry::trace::TraceId{trace_id_bin});
-  EXPECT_EQ(rec.log_record().trace_id(), expected_bytes);
-}
-
-TEST(OtlpLogRecordable, SetSpanId)
-{
-  OtlpLogRecordable rec;
   uint8_t span_id_bin[opentelemetry::trace::SpanId::kSize] = {'7', '6', '5', '4',
                                                               '3', '2', '1', '0'};
-  std::string expected_bytes;
-  expected_bytes.assign(
+  std::string expected_span_id_bytes;
+  expected_span_id_bytes.assign(
       reinterpret_cast<char *>(span_id_bin),
       reinterpret_cast<char *>(span_id_bin) + opentelemetry::trace::SpanId::kSize);
   rec.SetSpanId(opentelemetry::trace::SpanId{span_id_bin});
-  EXPECT_EQ(rec.log_record().span_id(), expected_bytes);
+
+  EXPECT_EQ(rec.log_record().time_unix_nano(), unix_start);
+  EXPECT_EQ(rec.log_record().severity_number(), proto::logs::v1::SEVERITY_NUMBER_ERROR);
+  EXPECT_EQ(rec.log_record().severity_text(), "ERROR");
+  EXPECT_EQ(rec.log_record().body().string_value(), name);
+  EXPECT_EQ(rec.log_record().trace_id(), expected_trace_id_bytes);
+  EXPECT_EQ(rec.log_record().span_id(), expected_span_id_bytes);
 }
 
-TEST(OtlpLogRecordable, SetResource)
+TEST(OtlpLogRecordable, GetResource)
 {
   OtlpLogRecordable rec;
   const std::string service_name_key = "service.name";
@@ -82,51 +66,21 @@ TEST(OtlpLogRecordable, SetResource)
       opentelemetry::sdk::resource::Resource::Create({{service_name_key, service_name}});
   rec.SetResource(resource);
 
-  auto proto_resource     = rec.ProtoResource();
-  bool found_service_name = false;
-  for (int i = 0; i < proto_resource.attributes_size(); i++)
-  {
-    auto attr = proto_resource.attributes(static_cast<int>(i));
-    if (attr.key() == service_name_key && attr.value().string_value() == service_name)
-    {
-      found_service_name = true;
-    }
-  }
-  EXPECT_TRUE(found_service_name);
+  EXPECT_EQ(&rec.GetResource(), &resource);
 }
 
 TEST(OtlpLogRecordable, DefaultResource)
 {
   OtlpLogRecordable rec;
 
-  auto proto_resource      = rec.ProtoResource();
-  int found_resource_count = 0;
-  for (int i = 0; i < proto_resource.attributes_size(); i++)
-  {
-    auto attr = proto_resource.attributes(static_cast<int>(i));
-    if (attr.key() == resource::SemanticConventions::kTelemetrySdkLanguage)
-    {
-      EXPECT_EQ(attr.value().string_value(), "cpp");
-      ++found_resource_count;
-    }
-    else if (attr.key() == resource::SemanticConventions::kTelemetrySdkName)
-    {
-      EXPECT_EQ(attr.value().string_value(), "opentelemetry");
-      ++found_resource_count;
-    }
-    else if (attr.key() == resource::SemanticConventions::kTelemetrySdkVersion)
-    {
-      EXPECT_EQ(attr.value().string_value(), OPENTELEMETRY_SDK_VERSION);
-      ++found_resource_count;
-    }
-  }
-  EXPECT_EQ(3, found_resource_count);
+  EXPECT_EQ(&rec.GetResource(), &opentelemetry::sdk::logs::ReadableLogRecord::GetDefaultResource());
 }
 
 // Test non-int single types. Int single types are tested using templates (see IntAttributeTest)
 TEST(OtlpLogRecordable, SetSingleAttribute)
 {
   OtlpLogRecordable rec;
+
   nostd::string_view bool_key = "bool_attr";
   common::AttributeValue bool_val(true);
   rec.SetAttribute(bool_key, bool_val);
@@ -139,21 +93,33 @@ TEST(OtlpLogRecordable, SetSingleAttribute)
   common::AttributeValue str_val(nostd::string_view("Test"));
   rec.SetAttribute(str_key, str_val);
 
-  EXPECT_EQ(rec.log_record().attributes(0).key(), bool_key);
-  EXPECT_EQ(rec.log_record().attributes(0).value().bool_value(), nostd::get<bool>(bool_val));
-
-  EXPECT_EQ(rec.log_record().attributes(1).key(), double_key);
-  EXPECT_EQ(rec.log_record().attributes(1).value().double_value(), nostd::get<double>(double_val));
-
-  EXPECT_EQ(rec.log_record().attributes(2).key(), str_key);
-  EXPECT_EQ(rec.log_record().attributes(2).value().string_value(),
-            nostd::get<nostd::string_view>(str_val).data());
+  int checked_attributes = 0;
+  for (auto &attribute : rec.log_record().attributes())
+  {
+    if (attribute.key() == bool_key)
+    {
+      ++checked_attributes;
+      EXPECT_EQ(attribute.value().bool_value(), nostd::get<bool>(bool_val));
+    }
+    else if (attribute.key() == double_key)
+    {
+      ++checked_attributes;
+      EXPECT_EQ(attribute.value().double_value(), nostd::get<double>(double_val));
+    }
+    else if (attribute.key() == str_key)
+    {
+      ++checked_attributes;
+      EXPECT_EQ(attribute.value().string_value(), nostd::get<nostd::string_view>(str_val).data());
+    }
+  }
+  EXPECT_EQ(3, checked_attributes);
 }
 
 // Test non-int array types. Int array types are tested using templates (see IntAttributeTest)
 TEST(OtlpLogRecordable, SetArrayAttribute)
 {
   OtlpLogRecordable rec;
+
   const int kArraySize = 3;
 
   bool bool_arr[kArraySize] = {true, false, true};
@@ -168,24 +134,42 @@ TEST(OtlpLogRecordable, SetArrayAttribute)
   nostd::span<const nostd::string_view> str_span(str_arr);
   rec.SetAttribute("str_arr_attr", str_span);
 
+  const opentelemetry::proto::common::v1::ArrayValue *bool_values   = nullptr;
+  const opentelemetry::proto::common::v1::ArrayValue *double_values = nullptr;
+  const opentelemetry::proto::common::v1::ArrayValue *string_values = nullptr;
+  for (int i = 0; i < rec.log_record().attributes_size(); i++)
+  {
+    if (rec.log_record().attributes(i).value().array_value().values(0).has_bool_value())
+    {
+      bool_values = &rec.log_record().attributes(i).value().array_value();
+    }
+    else if (rec.log_record().attributes(i).value().array_value().values(0).has_double_value())
+    {
+      double_values = &rec.log_record().attributes(i).value().array_value();
+    }
+    else if (rec.log_record().attributes(i).value().array_value().values(0).has_string_value())
+    {
+      string_values = &rec.log_record().attributes(i).value().array_value();
+    }
+  }
+
   for (int i = 0; i < kArraySize; i++)
   {
-    EXPECT_EQ(rec.log_record().attributes(0).value().array_value().values(i).bool_value(),
-              bool_span[i]);
-    EXPECT_EQ(rec.log_record().attributes(1).value().array_value().values(i).double_value(),
-              double_span[i]);
-    EXPECT_EQ(rec.log_record().attributes(2).value().array_value().values(i).string_value(),
-              str_span[i]);
+    EXPECT_EQ(bool_values->values(i).bool_value(), bool_span[i]);
+    EXPECT_EQ(double_values->values(i).double_value(), double_span[i]);
+    EXPECT_EQ(string_values->values(i).string_value(), str_span[i]);
   }
 }
 
 TEST(OtlpLogRecordable, SetInstrumentationScope)
 {
   OtlpLogRecordable rec;
+
   auto inst_lib =
       opentelemetry::sdk::instrumentationscope::InstrumentationScope::Create("test", "v1");
   rec.SetInstrumentationScope(*inst_lib);
-  EXPECT_EQ(rec.GetInstrumentationScope(), *inst_lib);
+
+  EXPECT_EQ(&rec.GetInstrumentationScope(), inst_lib.get());
 }
 
 /**
@@ -209,7 +193,9 @@ TYPED_TEST(OtlpLogRecordableIntAttributeTest, SetIntSingleAttribute)
   common::AttributeValue int_val(i);
 
   OtlpLogRecordable rec;
+
   rec.SetAttribute("int_attr", int_val);
+
   EXPECT_EQ(rec.log_record().attributes(0).value().int_value(), nostd::get<IntType>(int_val));
 }
 
@@ -222,6 +208,7 @@ TYPED_TEST(OtlpLogRecordableIntAttributeTest, SetIntArrayAttribute)
   nostd::span<const IntType> int_span(int_arr);
 
   OtlpLogRecordable rec;
+
   rec.SetAttribute("int_arr_attr", int_span);
 
   for (int i = 0; i < kArraySize; i++)
diff --git a/sdk/include/opentelemetry/sdk/common/circular_buffer.h b/sdk/include/opentelemetry/sdk/common/circular_buffer.h
index 6af9001833..9f34659352 100644
--- a/sdk/include/opentelemetry/sdk/common/circular_buffer.h
+++ b/sdk/include/opentelemetry/sdk/common/circular_buffer.h
@@ -115,6 +115,14 @@ class CircularBuffer
     return true;
   }
 
+  bool Add(std::unique_ptr<T> &&ptr) noexcept
+  {
+    // rvalue to lvalue reference
+    bool result = Add(std::ref(ptr));
+    ptr.reset();
+    return result;
+  }
+
   /**
    * Clear the circular buffer.
    *
diff --git a/sdk/include/opentelemetry/sdk/logs/batch_log_record_processor_factory.h b/sdk/include/opentelemetry/sdk/logs/batch_log_record_processor_factory.h
index a3ebb0e877..3177eb31e1 100644
--- a/sdk/include/opentelemetry/sdk/logs/batch_log_record_processor_factory.h
+++ b/sdk/include/opentelemetry/sdk/logs/batch_log_record_processor_factory.h
@@ -5,6 +5,8 @@
 
 #ifdef ENABLE_LOGS_PREVIEW
 
+#  include <memory>
+
 #  include "opentelemetry/sdk/logs/batch_log_record_processor_options.h"
 #  include "opentelemetry/sdk/logs/exporter.h"
 #  include "opentelemetry/sdk/logs/processor.h"
diff --git a/sdk/include/opentelemetry/sdk/logs/exporter.h b/sdk/include/opentelemetry/sdk/logs/exporter.h
index 02865dc204..a531577aef 100644
--- a/sdk/include/opentelemetry/sdk/logs/exporter.h
+++ b/sdk/include/opentelemetry/sdk/logs/exporter.h
@@ -6,6 +6,7 @@
 
 #  include <memory>
 #  include <vector>
+
 #  include "opentelemetry/nostd/span.h"
 #  include "opentelemetry/sdk/common/exporter_utils.h"
 #  include "opentelemetry/sdk/logs/processor.h"
@@ -27,7 +28,7 @@ class LogRecordExporter
   /**
    * Create a log recordable. This object will be used to record log data and
    * will subsequently be passed to LogRecordExporter::Export. Vendors can implement
-   * custom recordables or use the default LogRecord recordable provided by the
+   * custom recordables or use the default ReadWriteLogRecord recordable provided by the
    * SDK.
    * @return a newly initialized Recordable object
    *
diff --git a/sdk/include/opentelemetry/sdk/logs/log_record.h b/sdk/include/opentelemetry/sdk/logs/log_record.h
deleted file mode 100644
index 8b7ac19570..0000000000
--- a/sdk/include/opentelemetry/sdk/logs/log_record.h
+++ /dev/null
@@ -1,200 +0,0 @@
-// Copyright The OpenTelemetry Authors
-// SPDX-License-Identifier: Apache-2.0
-
-#pragma once
-#ifdef ENABLE_LOGS_PREVIEW
-
-#  include <map>
-#  include <unordered_map>
-#  include "opentelemetry/sdk/common/attribute_utils.h"
-#  include "opentelemetry/sdk/logs/recordable.h"
-#  include "opentelemetry/sdk/resource/resource.h"
-#  include "opentelemetry/version.h"
-
-OPENTELEMETRY_BEGIN_NAMESPACE
-namespace sdk
-{
-namespace logs
-{
-
-/**
- * A default Recordable implemenation to be passed in log statements,
- * matching the 10 fields of the Log Data Model.
- * (https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/data-model.md#log-and-event-record-definition)
- *
- */
-class LogRecord final : public Recordable
-{
-private:
-  // Default values are set by the respective data structures' constructors for all fields,
-  // except the severity field, which must be set manually (an enum with no default value).
-  opentelemetry::logs::Severity severity_                 = opentelemetry::logs::Severity::kInvalid;
-  const opentelemetry::sdk::resource::Resource *resource_ = nullptr;
-  common::AttributeMap attributes_map_;
-  std::string body_;  // Currently a simple string, but should be changed to "Any" type
-  opentelemetry::trace::TraceId trace_id_;
-  opentelemetry::trace::SpanId span_id_;
-  opentelemetry::trace::TraceFlags trace_flags_;
-  opentelemetry::common::SystemTimestamp timestamp_;  // uint64 nanoseconds since Unix epoch
-
-public:
-  /********** Setters for each field (overrides methods from the Recordable interface) ************/
-
-  /**
-   * Set the severity for this log.
-   * @param severity the severity of the event
-   */
-  void SetSeverity(opentelemetry::logs::Severity severity) noexcept override
-  {
-    severity_ = severity;
-  }
-
-  /**
-   * Set body field for this log.
-   * @param message the body to set
-   */
-  void SetBody(nostd::string_view message) noexcept override { body_ = std::string(message); }
-
-  /**
-   * Set Resource of this log
-   * @param Resource the resource to set
-   */
-  void SetResource(const opentelemetry::sdk::resource::Resource &resource) noexcept override
-  {
-    resource_ = &resource;
-  }
-
-  /**
-   * Set an attribute of a log.
-   * @param name the name of the attribute
-   * @param value the attribute value
-   */
-
-  void SetAttribute(nostd::string_view key,
-                    const opentelemetry::common::AttributeValue &value) noexcept override
-  {
-    attributes_map_.SetAttribute(key, value);
-  }
-
-  /**
-   * Set trace id for this log.
-   * @param trace_id the trace id to set
-   */
-  void SetTraceId(opentelemetry::trace::TraceId trace_id) noexcept override
-  {
-    trace_id_ = trace_id;
-  }
-
-  /**
-   * Set span id for this log.
-   * @param span_id the span id to set
-   */
-  virtual void SetSpanId(opentelemetry::trace::SpanId span_id) noexcept override
-  {
-    span_id_ = span_id;
-  }
-
-  /**
-   * Inject a trace_flags  for this log.
-   * @param trace_flags the span id to set
-   */
-  void SetTraceFlags(opentelemetry::trace::TraceFlags trace_flags) noexcept override
-  {
-    trace_flags_ = trace_flags;
-  }
-
-  /**
-   * Set the timestamp for this log.
-   * @param timestamp the timestamp of the event
-   */
-  void SetTimestamp(opentelemetry::common::SystemTimestamp timestamp) noexcept override
-  {
-    timestamp_ = timestamp;
-  }
-
-  /************************** Getters for each field ****************************/
-
-  /**
-   * Get the severity for this log
-   * @return the severity for this log
-   */
-  opentelemetry::logs::Severity GetSeverity() const noexcept { return severity_; }
-
-  /**
-   * Get the body of this log
-   * @return the body of this log
-   */
-  std::string GetBody() const noexcept { return body_; }
-
-  /**
-   * Get the resource for this log
-   * @return the resource for this log
-   */
-  const opentelemetry::sdk::resource::Resource &GetResource() const noexcept
-  {
-    if (nullptr == resource_)
-    {
-      return sdk::resource::Resource::GetDefault();
-    }
-    return *resource_;
-  }
-
-  /**
-   * Get the attributes for this log
-   * @return the attributes for this log
-   */
-  const std::unordered_map<std::string, common::OwnedAttributeValue> &GetAttributes() const noexcept
-  {
-    return attributes_map_.GetAttributes();
-  }
-
-  /**
-   * Get the trace id for this log
-   * @return the trace id for this log
-   */
-  opentelemetry::trace::TraceId GetTraceId() const noexcept { return trace_id_; }
-
-  /**
-   * Get the span id for this log
-   * @return the span id for this log
-   */
-  opentelemetry::trace::SpanId GetSpanId() const noexcept { return span_id_; }
-
-  /**
-   * Get the trace flags for this log
-   * @return the trace flags for this log
-   */
-  opentelemetry::trace::TraceFlags GetTraceFlags() const noexcept { return trace_flags_; }
-
-  /**
-   * Get the timestamp for this log
-   * @return the timestamp for this log
-   */
-  opentelemetry::common::SystemTimestamp GetTimestamp() const noexcept { return timestamp_; }
-
-  /**
-   * Set instrumentation_scope for this log.
-   * @param instrumentation_scope the instrumentation scope to set
-   */
-  void SetInstrumentationScope(const opentelemetry::sdk::instrumentationscope::InstrumentationScope
-                                   &instrumentation_scope) noexcept override
-  {
-    instrumentation_scope_ = &instrumentation_scope;
-  }
-
-  OPENTELEMETRY_DEPRECATED_MESSAGE("Please use SetInstrumentationScope instead")
-  void SetInstrumentationLibrary(
-      const opentelemetry::sdk::instrumentationscope::InstrumentationScope
-          &instrumentation_scope) noexcept
-  {
-    SetInstrumentationScope(instrumentation_scope);
-  }
-
-private:
-  const opentelemetry::sdk::instrumentationscope::InstrumentationScope *instrumentation_scope_ =
-      nullptr;
-};
-}  // namespace logs
-}  // namespace sdk
-OPENTELEMETRY_END_NAMESPACE
-#endif
diff --git a/sdk/include/opentelemetry/sdk/logs/logger.h b/sdk/include/opentelemetry/sdk/logs/logger.h
index 7e4429e0cf..d290df98c3 100644
--- a/sdk/include/opentelemetry/sdk/logs/logger.h
+++ b/sdk/include/opentelemetry/sdk/logs/logger.h
@@ -4,14 +4,16 @@
 #pragma once
 #ifdef ENABLE_LOGS_PREVIEW
 
+#  include <memory>
+#  include <vector>
+
 #  include "opentelemetry/common/macros.h"
 #  include "opentelemetry/logs/logger.h"
+#  include "opentelemetry/nostd/unique_ptr.h"
 #  include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h"
 #  include "opentelemetry/sdk/logs/logger_context.h"
 #  include "opentelemetry/sdk/logs/logger_provider.h"
 
-#  include <vector>
-
 OPENTELEMETRY_BEGIN_NAMESPACE
 namespace sdk
 {
@@ -38,38 +40,12 @@ class Logger final : public opentelemetry::logs::Logger
    */
   const opentelemetry::nostd::string_view GetName() noexcept override;
 
-  /**
-   * Writes a log record into the processor.
-   * @param severity the severity level of the log event.
-   * @param message the string message of the log (perhaps support std::fmt or fmt-lib format).
-   * with the log event.
-   * @param attributes the attributes, stored as a 2D list of key/value pairs, that are associated
-   * with the log event.
-   * @param trace_id the trace id associated with the log event.
-   * @param span_id the span id associate with the log event.
-   * @param trace_flags the trace flags associated with the log event.
-   * @param timestamp the timestamp the log record was created.
-   * @throws No exceptions under any circumstances.   */
-  void Log(opentelemetry::logs::Severity severity,
-           nostd::string_view body,
-           const opentelemetry::common::KeyValueIterable &attributes,
-           opentelemetry::trace::TraceId trace_id,
-           opentelemetry::trace::SpanId span_id,
-           opentelemetry::trace::TraceFlags trace_flags,
-           opentelemetry::common::SystemTimestamp timestamp) noexcept override;
+  nostd::unique_ptr<opentelemetry::logs::LogRecord> CreateLogRecord() noexcept override;
 
-  // Deprecated
-  void Log(opentelemetry::logs::Severity severity,
-           nostd::string_view /* name */,
-           nostd::string_view body,
-           const opentelemetry::common::KeyValueIterable &attributes,
-           opentelemetry::trace::TraceId trace_id,
-           opentelemetry::trace::SpanId span_id,
-           opentelemetry::trace::TraceFlags trace_flags,
-           opentelemetry::common::SystemTimestamp timestamp) noexcept override
-  {
-    Log(severity, body, attributes, trace_id, span_id, trace_flags, timestamp);
-  }
+  using opentelemetry::logs::Logger::EmitLogRecord;
+
+  void EmitLogRecord(
+      nostd::unique_ptr<opentelemetry::logs::LogRecord> &&log_record) noexcept override;
 
   /** Returns the associated instrumentation scope */
   const opentelemetry::sdk::instrumentationscope::InstrumentationScope &GetInstrumentationScope()
diff --git a/sdk/include/opentelemetry/sdk/logs/logger_context.h b/sdk/include/opentelemetry/sdk/logs/logger_context.h
index da41c122b7..fe6db6822a 100644
--- a/sdk/include/opentelemetry/sdk/logs/logger_context.h
+++ b/sdk/include/opentelemetry/sdk/logs/logger_context.h
@@ -5,6 +5,8 @@
 
 #ifdef ENABLE_LOGS_PREVIEW
 
+#  include <memory>
+
 #  include "opentelemetry/sdk/logs/processor.h"
 #  include "opentelemetry/sdk/resource/resource.h"
 #  include "opentelemetry/version.h"
diff --git a/sdk/include/opentelemetry/sdk/logs/logger_context_factory.h b/sdk/include/opentelemetry/sdk/logs/logger_context_factory.h
index 28dc24d56c..377094d5db 100644
--- a/sdk/include/opentelemetry/sdk/logs/logger_context_factory.h
+++ b/sdk/include/opentelemetry/sdk/logs/logger_context_factory.h
@@ -5,6 +5,8 @@
 
 #ifdef ENABLE_LOGS_PREVIEW
 
+#  include <memory>
+
 #  include "opentelemetry/sdk/logs/logger_context.h"
 #  include "opentelemetry/sdk/logs/processor.h"
 #  include "opentelemetry/sdk/resource/resource.h"
diff --git a/sdk/include/opentelemetry/sdk/logs/multi_recordable.h b/sdk/include/opentelemetry/sdk/logs/multi_recordable.h
index 9b37806e2a..8bbc2ae200 100644
--- a/sdk/include/opentelemetry/sdk/logs/multi_recordable.h
+++ b/sdk/include/opentelemetry/sdk/logs/multi_recordable.h
@@ -37,6 +37,12 @@ class MultiRecordable final : public Recordable
    */
   void SetTimestamp(opentelemetry::common::SystemTimestamp timestamp) noexcept override;
 
+  /**
+   * Set the observed timestamp for this log.
+   * @param timestamp the timestamp to set
+   */
+  void SetObservedTimestamp(opentelemetry::common::SystemTimestamp timestamp) noexcept override;
+
   /**
    * Set the severity for this log.
    * @param severity the severity of the event
@@ -47,39 +53,39 @@ class MultiRecordable final : public Recordable
    * Set body field for this log.
    * @param message the body to set
    */
-  void SetBody(nostd::string_view message) noexcept override;
-
-  /**
-   * Set Resource of this log
-   * @param Resource the resource to set
-   */
-  void SetResource(const opentelemetry::sdk::resource::Resource &resource) noexcept override;
-
-  /**
-   * Set an attribute of a log.
-   * @param key the name of the attribute
-   * @param value the attribute value
-   */
-  void SetAttribute(nostd::string_view key,
-                    const opentelemetry::common::AttributeValue &value) noexcept override;
+  void SetBody(const opentelemetry::common::AttributeValue &message) noexcept override;
 
   /**
    * Set the trace id for this log.
    * @param trace_id the trace id to set
    */
-  void SetTraceId(opentelemetry::trace::TraceId trace_id) noexcept override;
+  void SetTraceId(const opentelemetry::trace::TraceId &trace_id) noexcept override;
 
   /**
    * Set the span id for this log.
    * @param span_id the span id to set
    */
-  void SetSpanId(opentelemetry::trace::SpanId span_id) noexcept override;
+  void SetSpanId(const opentelemetry::trace::SpanId &span_id) noexcept override;
 
   /**
    * Inject trace_flags for this log.
    * @param trace_flags the trace flags to set
    */
-  void SetTraceFlags(opentelemetry::trace::TraceFlags trace_flags) noexcept override;
+  void SetTraceFlags(const opentelemetry::trace::TraceFlags &trace_flags) noexcept override;
+
+  /**
+   * Set an attribute of a log.
+   * @param key the name of the attribute
+   * @param value the attribute value
+   */
+  void SetAttribute(nostd::string_view key,
+                    const opentelemetry::common::AttributeValue &value) noexcept override;
+
+  /**
+   * Set Resource of this log
+   * @param Resource the resource to set
+   */
+  void SetResource(const opentelemetry::sdk::resource::Resource &resource) noexcept override;
 
   /**
    * Set instrumentation_scope for this log.
@@ -88,21 +94,8 @@ class MultiRecordable final : public Recordable
   void SetInstrumentationScope(const opentelemetry::sdk::instrumentationscope::InstrumentationScope
                                    &instrumentation_scope) noexcept override;
 
-  /** Returns the associated instrumentation scope */
-  const opentelemetry::sdk::instrumentationscope::InstrumentationScope &GetInstrumentationScope()
-      const noexcept;
-
-  OPENTELEMETRY_DEPRECATED_MESSAGE("Please use GetInstrumentationScope instead")
-  const opentelemetry::sdk::instrumentationscope::InstrumentationScope &GetInstrumentationLibrary()
-      const noexcept
-  {
-    return GetInstrumentationScope();
-  }
-
 private:
   std::unordered_map<std::size_t, std::unique_ptr<Recordable>> recordables_;
-  const opentelemetry::sdk::instrumentationscope::InstrumentationScope *instrumentation_scope_ =
-      nullptr;
 };
 }  // namespace logs
 }  // namespace sdk
diff --git a/sdk/include/opentelemetry/sdk/logs/processor.h b/sdk/include/opentelemetry/sdk/logs/processor.h
index 536140f7db..97f823a25b 100644
--- a/sdk/include/opentelemetry/sdk/logs/processor.h
+++ b/sdk/include/opentelemetry/sdk/logs/processor.h
@@ -6,6 +6,8 @@
 
 #  include <chrono>
 #  include <memory>
+
+#  include "opentelemetry/nostd/unique_ptr.h"
 #  include "opentelemetry/sdk/logs/recordable.h"
 
 OPENTELEMETRY_BEGIN_NAMESPACE
@@ -25,6 +27,7 @@ class LogRecordProcessor
   /**
    * Create a log recordable. This requests a new log recordable from the
    * associated exporter.
+   *
    * @return a newly initialized recordable
    *
    * Note: This method must be callable from multiple threads.
@@ -33,7 +36,7 @@ class LogRecordProcessor
 
   /**
    * OnEmit is called by the SDK once a log record has been successfully created.
-   * @param record the log record
+   * @param record the log recordable object
    */
   virtual void OnEmit(std::unique_ptr<Recordable> &&record) noexcept = 0;
 
diff --git a/sdk/include/opentelemetry/sdk/logs/read_write_log_record.h b/sdk/include/opentelemetry/sdk/logs/read_write_log_record.h
new file mode 100644
index 0000000000..c81a2da126
--- /dev/null
+++ b/sdk/include/opentelemetry/sdk/logs/read_write_log_record.h
@@ -0,0 +1,191 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+#ifdef ENABLE_LOGS_PREVIEW
+
+#  include <memory>
+#  include <string>
+
+#  include "opentelemetry/common/attribute_value.h"
+#  include "opentelemetry/common/key_value_iterable.h"
+#  include "opentelemetry/common/timestamp.h"
+#  include "opentelemetry/logs/log_record.h"
+#  include "opentelemetry/logs/severity.h"
+#  include "opentelemetry/nostd/unique_ptr.h"
+#  include "opentelemetry/sdk/common/attribute_utils.h"
+#  include "opentelemetry/sdk/common/empty_attributes.h"
+#  include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h"
+#  include "opentelemetry/sdk/logs/readable_log_record.h"
+#  include "opentelemetry/sdk/resource/resource.h"
+#  include "opentelemetry/trace/span_id.h"
+#  include "opentelemetry/trace/trace_flags.h"
+#  include "opentelemetry/trace/trace_id.h"
+#  include "opentelemetry/version.h"
+
+OPENTELEMETRY_BEGIN_NAMESPACE
+namespace sdk
+{
+namespace logs
+{
+/**
+ * Maintains a representation of a log in a format that can be processed by a recorder.
+ *
+ * This class is thread-compatible.
+ */
+class ReadWriteLogRecord final : public ReadableLogRecord
+{
+public:
+  ReadWriteLogRecord();
+  ~ReadWriteLogRecord() override;
+
+  /**
+   * Set the timestamp for this log.
+   * @param timestamp the timestamp to set
+   */
+  void SetTimestamp(opentelemetry::common::SystemTimestamp timestamp) noexcept override;
+
+  /**
+   * Get the timestamp of this log.
+   * @return the timestamp of this log
+   */
+  opentelemetry::common::SystemTimestamp GetTimestamp() const noexcept override;
+
+  /**
+   * Set the observed timestamp for this log.
+   * @param timestamp the timestamp to set
+   */
+  void SetObservedTimestamp(opentelemetry::common::SystemTimestamp timestamp) noexcept override;
+
+  /**
+   * Get the observed timestamp of this log.
+   * @return the observed timestamp of this log
+   */
+  opentelemetry::common::SystemTimestamp GetObservedTimestamp() const noexcept override;
+
+  /**
+   * Set the severity for this log.
+   * @param severity the severity of the event
+   */
+  void SetSeverity(opentelemetry::logs::Severity severity) noexcept override;
+
+  /**
+   * Get the severity of this log.
+   * @return the severity of this log
+   */
+  opentelemetry::logs::Severity GetSeverity() const noexcept override;
+
+  /**
+   * Set body field for this log.
+   * @param message the body to set
+   */
+  void SetBody(const opentelemetry::common::AttributeValue &message) noexcept override;
+
+  /**
+   * Get body field of this log.
+   * @return the body field for this log.
+   */
+  const opentelemetry::common::AttributeValue &GetBody() const noexcept override;
+
+  /**
+   * Set the trace id for this log.
+   * @param trace_id the trace id to set
+   */
+  void SetTraceId(const opentelemetry::trace::TraceId &trace_id) noexcept override;
+
+  /**
+   * Get the trace id of this log.
+   * @return the trace id of this log
+   */
+  const opentelemetry::trace::TraceId &GetTraceId() const noexcept override;
+
+  /**
+   * Set the span id for this log.
+   * @param span_id the span id to set
+   */
+  void SetSpanId(const opentelemetry::trace::SpanId &span_id) noexcept override;
+
+  /**
+   * Get the span id of this log.
+   * @return the span id of this log
+   */
+  const opentelemetry::trace::SpanId &GetSpanId() const noexcept override;
+
+  /**
+   * Inject trace_flags for this log.
+   * @param trace_flags the trace flags to set
+   */
+  void SetTraceFlags(const opentelemetry::trace::TraceFlags &trace_flags) noexcept override;
+
+  /**
+   * Inject trace_flags of this log.
+   * @return trace_flags of this log
+   */
+  const opentelemetry::trace::TraceFlags &GetTraceFlags() const noexcept override;
+
+  /**
+   * Set an attribute of a log.
+   * @param key the name of the attribute
+   * @param value the attribute value
+   */
+  void SetAttribute(nostd::string_view key,
+                    const opentelemetry::common::AttributeValue &value) noexcept override;
+
+  /**
+   * 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;
+
+  /**
+   * Get resource of this log
+   * @return the resource of this log
+   */
+  const opentelemetry::sdk::resource::Resource &GetResource() const noexcept override;
+
+  /**
+   * Set Resource of this log
+   * @param Resource the resource to set
+   */
+  void SetResource(const opentelemetry::sdk::resource::Resource &resource) noexcept override;
+
+  /**
+   * Get instrumentation_scope of this log.
+   * @return  the instrumentation_scope of this log
+   */
+  const opentelemetry::sdk::instrumentationscope::InstrumentationScope &GetInstrumentationScope()
+      const noexcept override;
+
+  /**
+   * Set instrumentation_scope for this log.
+   * @param instrumentation_scope the instrumentation scope to set
+   */
+  void SetInstrumentationScope(const opentelemetry::sdk::instrumentationscope::InstrumentationScope
+                                   &instrumentation_scope) noexcept override;
+
+private:
+  // Default values are set by the respective data structures' constructors for all fields,
+  // except the severity field, which must be set manually (an enum with no default value).
+  opentelemetry::logs::Severity severity_;
+  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_;
+  opentelemetry::common::SystemTimestamp timestamp_;
+  opentelemetry::common::SystemTimestamp observed_timestamp_;
+
+  // We do not pay for trace state when not necessary
+  struct TraceState
+  {
+    opentelemetry::trace::TraceId trace_id;
+    opentelemetry::trace::SpanId span_id;
+    opentelemetry::trace::TraceFlags trace_flags;
+  };
+  std::unique_ptr<TraceState> trace_state_;
+};
+}  // namespace logs
+}  // namespace sdk
+OPENTELEMETRY_END_NAMESPACE
+#endif
diff --git a/sdk/include/opentelemetry/sdk/logs/readable_log_record.h b/sdk/include/opentelemetry/sdk/logs/readable_log_record.h
new file mode 100644
index 0000000000..7b3d8ad502
--- /dev/null
+++ b/sdk/include/opentelemetry/sdk/logs/readable_log_record.h
@@ -0,0 +1,121 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+#ifdef ENABLE_LOGS_PREVIEW
+
+#  include <string>
+#  include <unordered_map>
+
+#  include "opentelemetry/common/attribute_value.h"
+#  include "opentelemetry/common/key_value_iterable.h"
+#  include "opentelemetry/common/timestamp.h"
+#  include "opentelemetry/logs/severity.h"
+#  include "opentelemetry/sdk/common/attribute_utils.h"
+#  include "opentelemetry/sdk/common/empty_attributes.h"
+#  include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h"
+#  include "opentelemetry/sdk/logs/recordable.h"
+#  include "opentelemetry/sdk/resource/resource.h"
+#  include "opentelemetry/trace/span_id.h"
+#  include "opentelemetry/trace/trace_flags.h"
+#  include "opentelemetry/trace/trace_id.h"
+#  include "opentelemetry/version.h"
+
+OPENTELEMETRY_BEGIN_NAMESPACE
+namespace sdk
+{
+namespace logs
+{
+/**
+ * Maintains a representation of a log in a format that can be processed by a recorder.
+ *
+ * This class is thread-compatible.
+ */
+class ReadableLogRecord : public Recordable
+{
+public:
+  /**
+   * Get the timestamp of this log.
+   * @return the timestamp of this log
+   */
+  virtual opentelemetry::common::SystemTimestamp GetTimestamp() const noexcept = 0;
+
+  /**
+   * Get the observed timestamp of this log.
+   * @return the observed timestamp of this log
+   */
+  virtual opentelemetry::common::SystemTimestamp GetObservedTimestamp() const noexcept = 0;
+
+  /**
+   * Get the severity of this log.
+   * @return the severity of this log
+   */
+  virtual opentelemetry::logs::Severity GetSeverity() const noexcept = 0;
+
+  /**
+   * Get the severity text of this log.
+   * @return the severity text for this log
+   */
+  virtual nostd::string_view GetSeverityText() const noexcept;
+
+  /**
+   * Get body field of this log.
+   * @return the body field for this log.
+   */
+  virtual const opentelemetry::common::AttributeValue &GetBody() const noexcept = 0;
+
+  /**
+   * Get the trace id of this log.
+   * @return the trace id of this log
+   */
+  virtual const opentelemetry::trace::TraceId &GetTraceId() const noexcept = 0;
+
+  /**
+   * Get the span id of this log.
+   * @return the span id of this log
+   */
+  virtual const opentelemetry::trace::SpanId &GetSpanId() const noexcept = 0;
+
+  /**
+   * Inject trace_flags of this log.
+   * @return trace_flags of this log
+   */
+  virtual const opentelemetry::trace::TraceFlags &GetTraceFlags() const noexcept = 0;
+
+  /**
+   * Get attributes of this log.
+   * @return the body field of this log
+   */
+  virtual const std::unordered_map<std::string, opentelemetry::common::AttributeValue>
+      &GetAttributes() const noexcept = 0;
+
+  /**
+   * Get resource of this log
+   * @return the resource of this log
+   */
+  virtual const opentelemetry::sdk::resource::Resource &GetResource() const noexcept = 0;
+
+  /**
+   * Get instrumentation scope of this log.
+   * @return  the instrumentation scope of this log
+   */
+  virtual const opentelemetry::sdk::instrumentationscope::InstrumentationScope &
+  GetInstrumentationScope() const noexcept = 0;
+
+  /**
+   * Get default instrumentation scope of logs.
+   * @return  the default instrumentation scope of logs
+   */
+  static const opentelemetry::sdk::instrumentationscope::InstrumentationScope &
+  GetDefaultInstrumentationScope() noexcept;
+
+  /**
+   * Get default resource of logs.
+   * @return  the default resource of logs
+   */
+  static const opentelemetry::sdk::resource::Resource &GetDefaultResource() noexcept;
+};
+}  // namespace logs
+}  // namespace sdk
+OPENTELEMETRY_END_NAMESPACE
+#endif
diff --git a/sdk/include/opentelemetry/sdk/logs/recordable.h b/sdk/include/opentelemetry/sdk/logs/recordable.h
index 13e834ee49..983a2e6b57 100644
--- a/sdk/include/opentelemetry/sdk/logs/recordable.h
+++ b/sdk/include/opentelemetry/sdk/logs/recordable.h
@@ -7,12 +7,13 @@
 #  include "opentelemetry/common/attribute_value.h"
 #  include "opentelemetry/common/key_value_iterable.h"
 #  include "opentelemetry/common/timestamp.h"
+#  include "opentelemetry/logs/log_record.h"
 #  include "opentelemetry/logs/severity.h"
+#  include "opentelemetry/sdk/common/attribute_utils.h"
 #  include "opentelemetry/sdk/common/empty_attributes.h"
 #  include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h"
+#  include "opentelemetry/sdk/logs/recordable.h"
 #  include "opentelemetry/sdk/resource/resource.h"
-#  include "opentelemetry/trace/span.h"
-#  include "opentelemetry/trace/span_context.h"
 #  include "opentelemetry/trace/span_id.h"
 #  include "opentelemetry/trace/trace_flags.h"
 #  include "opentelemetry/trace/trace_id.h"
@@ -28,61 +29,16 @@ namespace logs
  *
  * This class is thread-compatible.
  */
-class Recordable
+
+class Recordable : public opentelemetry::logs::LogRecord
 {
 public:
-  virtual ~Recordable() = default;
-
-  /**
-   * Set the timestamp for this log.
-   * @param timestamp the timestamp to set
-   */
-  virtual void SetTimestamp(opentelemetry::common::SystemTimestamp timestamp) noexcept = 0;
-
-  /**
-   * Set the severity for this log.
-   * @param severity the severity of the event
-   */
-  virtual void SetSeverity(opentelemetry::logs::Severity severity) noexcept = 0;
-
-  /**
-   * Set body field for this log.
-   * @param message the body to set
-   */
-  virtual void SetBody(nostd::string_view message) noexcept = 0;
-
   /**
    * Set Resource of this log
    * @param Resource the resource to set
    */
   virtual void SetResource(const opentelemetry::sdk::resource::Resource &resource) noexcept = 0;
 
-  /**
-   * Set an attribute of a log.
-   * @param key the name of the attribute
-   * @param value the attribute value
-   */
-  virtual void SetAttribute(nostd::string_view key,
-                            const opentelemetry::common::AttributeValue &value) noexcept = 0;
-
-  /**
-   * Set the trace id for this log.
-   * @param trace_id the trace id to set
-   */
-  virtual void SetTraceId(opentelemetry::trace::TraceId trace_id) noexcept = 0;
-
-  /**
-   * Set the span id for this log.
-   * @param span_id the span id to set
-   */
-  virtual void SetSpanId(opentelemetry::trace::SpanId span_id) noexcept = 0;
-
-  /**
-   * Inject trace_flags for this log.
-   * @param trace_flags the trace flags to set
-   */
-  virtual void SetTraceFlags(opentelemetry::trace::TraceFlags trace_flags) noexcept = 0;
-
   /**
    * Set instrumentation_scope for this log.
    * @param instrumentation_scope the instrumentation scope to set
@@ -90,15 +46,8 @@ class Recordable
   virtual void SetInstrumentationScope(
       const opentelemetry::sdk::instrumentationscope::InstrumentationScope
           &instrumentation_scope) noexcept = 0;
-
-  OPENTELEMETRY_DEPRECATED_MESSAGE("Please use SetInstrumentationScope instead")
-  void SetInstrumentationLibrary(
-      const opentelemetry::sdk::instrumentationscope::InstrumentationScope
-          &instrumentation_scope) noexcept
-  {
-    SetInstrumentationScope(instrumentation_scope);
-  }
 };
+
 }  // namespace logs
 }  // namespace sdk
 OPENTELEMETRY_END_NAMESPACE
diff --git a/sdk/include/opentelemetry/sdk/logs/simple_log_record_processor.h b/sdk/include/opentelemetry/sdk/logs/simple_log_record_processor.h
index 0e4ce3f2ab..f3d6bc27c3 100644
--- a/sdk/include/opentelemetry/sdk/logs/simple_log_record_processor.h
+++ b/sdk/include/opentelemetry/sdk/logs/simple_log_record_processor.h
@@ -5,6 +5,7 @@
 #ifdef ENABLE_LOGS_PREVIEW
 
 #  include <atomic>
+#  include <memory>
 #  include <mutex>
 
 #  include "opentelemetry/common/spin_lock_mutex.h"
diff --git a/sdk/include/opentelemetry/sdk/logs/simple_log_record_processor_factory.h b/sdk/include/opentelemetry/sdk/logs/simple_log_record_processor_factory.h
index 7ba2c3ac0f..a71ba05684 100644
--- a/sdk/include/opentelemetry/sdk/logs/simple_log_record_processor_factory.h
+++ b/sdk/include/opentelemetry/sdk/logs/simple_log_record_processor_factory.h
@@ -5,6 +5,8 @@
 
 #ifdef ENABLE_LOGS_PREVIEW
 
+#  include <memory>
+
 #  include "opentelemetry/sdk/logs/exporter.h"
 #  include "opentelemetry/sdk/logs/processor.h"
 
diff --git a/sdk/src/logs/CMakeLists.txt b/sdk/src/logs/CMakeLists.txt
index 546e66614b..ff43c4d92b 100644
--- a/sdk/src/logs/CMakeLists.txt
+++ b/sdk/src/logs/CMakeLists.txt
@@ -11,7 +11,9 @@ add_library(
   logger_context_factory.cc
   multi_log_record_processor.cc
   multi_log_record_processor_factory.cc
-  multi_recordable.cc)
+  multi_recordable.cc
+  read_write_log_record.cc
+  readable_log_record.cc)
 
 set_target_properties(opentelemetry_logs PROPERTIES EXPORT_NAME logs)
 
diff --git a/sdk/src/logs/batch_log_record_processor.cc b/sdk/src/logs/batch_log_record_processor.cc
index 87992e7822..7903c3b24c 100644
--- a/sdk/src/logs/batch_log_record_processor.cc
+++ b/sdk/src/logs/batch_log_record_processor.cc
@@ -61,7 +61,7 @@ void BatchLogRecordProcessor::OnEmit(std::unique_ptr<Recordable> &&record) noexc
     return;
   }
 
-  if (buffer_.Add(record) == false)
+  if (buffer_.Add(std::unique_ptr<Recordable>(record.release())) == false)
   {
     return;
   }
diff --git a/sdk/src/logs/logger.cc b/sdk/src/logs/logger.cc
index d7ab485dff..a87e4af8a2 100644
--- a/sdk/src/logs/logger.cc
+++ b/sdk/src/logs/logger.cc
@@ -3,7 +3,6 @@
 
 #ifdef ENABLE_LOGS_PREVIEW
 #  include "opentelemetry/sdk/logs/logger.h"
-#  include "opentelemetry/sdk/logs/log_record.h"
 #  include "opentelemetry/sdk_config.h"
 #  include "opentelemetry/trace/provider.h"
 
@@ -30,88 +29,31 @@ const nostd::string_view Logger::GetName() noexcept
   return logger_name_;
 }
 
-/**
- * Create and populate recordable with the log event's fields passed in.
- * The timestamp, severity, traceid, spanid, and traceflags, are injected
- * if the user does not specify them.
- */
-void Logger::Log(opentelemetry::logs::Severity severity,
-                 nostd::string_view body,
-                 const common::KeyValueIterable &attributes,
-                 trace_api::TraceId trace_id,
-                 trace_api::SpanId span_id,
-                 trace_api::TraceFlags trace_flags,
-                 common::SystemTimestamp timestamp) noexcept
+nostd::unique_ptr<opentelemetry::logs::LogRecord> Logger::CreateLogRecord() noexcept
 {
-  // If this logger does not have a processor, no need to create a log record
+  // If this logger does not have a processor, no need to create a log recordable
   if (!context_)
   {
-    return;
+    return nullptr;
   }
-  auto &processor = context_->GetProcessor();
 
-  // TODO: Sampler (should include check for minSeverity)
+  return nostd::unique_ptr<opentelemetry::logs::LogRecord>(
+      context_->GetProcessor().MakeRecordable().release());
+}
 
-  auto recordable = processor.MakeRecordable();
-  if (recordable == nullptr)
+void Logger::EmitLogRecord(nostd::unique_ptr<opentelemetry::logs::LogRecord> &&log_record) noexcept
+{
+  if (!log_record)
   {
-    OTEL_INTERNAL_LOG_ERROR("[LOGGER] Recordable creation failed");
     return;
   }
 
-  // Populate recordable fields
-  recordable->SetTimestamp(timestamp);
-  recordable->SetSeverity(severity);
-  recordable->SetBody(body);
-  recordable->SetInstrumentationScope(GetInstrumentationScope());
-
-  recordable->SetResource(context_->GetResource());
-
-  attributes.ForEachKeyValue([&](nostd::string_view key, common::AttributeValue value) noexcept {
-    recordable->SetAttribute(key, value);
-    return true;
-  });
-
-  // Inject trace_id/span_id/trace_flags if none is set by user
-  auto provider     = trace_api::Provider::GetTracerProvider();
-  auto tracer       = provider->GetTracer(logger_name_);
-  auto span_context = tracer->GetCurrentSpan()->GetContext();
-
-  // Leave these fields in the recordable empty if neither the passed in values
-  // nor the context values are valid (e.g. the application is not using traces)
-
-  // TraceId
-  if (trace_id.IsValid())
-  {
-    recordable->SetTraceId(trace_id);
-  }
-  else if (span_context.trace_id().IsValid())
-  {
-    recordable->SetTraceId(span_context.trace_id());
-  }
-
-  // SpanId
-  if (span_id.IsValid())
-  {
-    recordable->SetSpanId(span_id);
-  }
-  else if (span_context.span_id().IsValid())
-  {
-    recordable->SetSpanId(span_context.span_id());
-  }
+  auto &processor = context_->GetProcessor();
 
-  // TraceFlags
-  if (trace_flags.IsSampled())
-  {
-    recordable->SetTraceFlags(trace_flags);
-  }
-  else if (span_context.trace_flags().IsSampled())
-  {
-    recordable->SetTraceFlags(span_context.trace_flags());
-  }
+  // TODO: Sampler (should include check for minSeverity)
 
-  // Send the log record to the processor
-  processor.OnEmit(std::move(recordable));
+  // Send the log recordable to the processor
+  processor.OnEmit(std::unique_ptr<Recordable>(static_cast<Recordable *>(log_record.release())));
 }
 
 const opentelemetry::sdk::instrumentationscope::InstrumentationScope &
diff --git a/sdk/src/logs/logger_provider.cc b/sdk/src/logs/logger_provider.cc
index 6f806d5be9..3000aa8230 100644
--- a/sdk/src/logs/logger_provider.cc
+++ b/sdk/src/logs/logger_provider.cc
@@ -91,7 +91,7 @@ nostd::shared_ptr<opentelemetry::logs::Logger> LoggerProvider::GetLogger(
   */
 
   // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/data-model.md#field-instrumentationscope
-  opentelemetry::nostd::unique_ptr<instrumentationscope::InstrumentationScope> lib;
+  std::unique_ptr<instrumentationscope::InstrumentationScope> lib;
   if (library_name.empty())
   {
     lib = instrumentationscope::InstrumentationScope::Create(logger_name, library_version,
diff --git a/sdk/src/logs/multi_log_record_processor.cc b/sdk/src/logs/multi_log_record_processor.cc
index a6f20be7eb..2859126f80 100644
--- a/sdk/src/logs/multi_log_record_processor.cc
+++ b/sdk/src/logs/multi_log_record_processor.cc
@@ -40,7 +40,7 @@ void MultiLogRecordProcessor::AddProcessor(std::unique_ptr<LogRecordProcessor> &
 
 std::unique_ptr<Recordable> MultiLogRecordProcessor::MakeRecordable() noexcept
 {
-  auto recordable       = std::unique_ptr<Recordable>(new MultiRecordable);
+  auto recordable       = std::unique_ptr<Recordable>(new MultiRecordable());
   auto multi_recordable = static_cast<MultiRecordable *>(recordable.get());
   for (auto &processor : processors_)
   {
diff --git a/sdk/src/logs/multi_recordable.cc b/sdk/src/logs/multi_recordable.cc
index c91ffc6b82..17e2c29777 100644
--- a/sdk/src/logs/multi_recordable.cc
+++ b/sdk/src/logs/multi_recordable.cc
@@ -60,7 +60,22 @@ void MultiRecordable::SetTimestamp(opentelemetry::common::SystemTimestamp timest
 {
   for (auto &recordable : recordables_)
   {
-    recordable.second->SetTimestamp(timestamp);
+    if (recordable.second)
+    {
+      recordable.second->SetTimestamp(timestamp);
+    }
+  }
+}
+
+void MultiRecordable::SetObservedTimestamp(
+    opentelemetry::common::SystemTimestamp timestamp) noexcept
+{
+  for (auto &recordable : recordables_)
+  {
+    if (recordable.second)
+    {
+      recordable.second->SetObservedTimestamp(timestamp);
+    }
   }
 }
 
@@ -68,56 +83,77 @@ void MultiRecordable::SetSeverity(opentelemetry::logs::Severity severity) noexce
 {
   for (auto &recordable : recordables_)
   {
-    recordable.second->SetSeverity(severity);
+    if (recordable.second)
+    {
+      recordable.second->SetSeverity(severity);
+    }
   }
 }
 
-void MultiRecordable::SetBody(nostd::string_view message) noexcept
+void MultiRecordable::SetBody(const opentelemetry::common::AttributeValue &message) noexcept
 {
   for (auto &recordable : recordables_)
   {
-    recordable.second->SetBody(message);
+    if (recordable.second)
+    {
+      recordable.second->SetBody(message);
+    }
   }
 }
 
-void MultiRecordable::SetResource(const opentelemetry::sdk::resource::Resource &resource) noexcept
+void MultiRecordable::SetTraceId(const opentelemetry::trace::TraceId &trace_id) noexcept
 {
   for (auto &recordable : recordables_)
   {
-    recordable.second->SetResource(resource);
+    if (recordable.second)
+    {
+      recordable.second->SetTraceId(trace_id);
+    }
   }
 }
 
-void MultiRecordable::SetAttribute(nostd::string_view key,
-                                   const opentelemetry::common::AttributeValue &value) noexcept
+void MultiRecordable::SetSpanId(const opentelemetry::trace::SpanId &span_id) noexcept
 {
   for (auto &recordable : recordables_)
   {
-    recordable.second->SetAttribute(key, value);
+    if (recordable.second)
+    {
+      recordable.second->SetSpanId(span_id);
+    }
   }
 }
 
-void MultiRecordable::SetTraceId(opentelemetry::trace::TraceId trace_id) noexcept
+void MultiRecordable::SetTraceFlags(const opentelemetry::trace::TraceFlags &trace_flags) noexcept
 {
   for (auto &recordable : recordables_)
   {
-    recordable.second->SetTraceId(trace_id);
+    if (recordable.second)
+    {
+      recordable.second->SetTraceFlags(trace_flags);
+    }
   }
 }
 
-void MultiRecordable::SetSpanId(opentelemetry::trace::SpanId span_id) noexcept
+void MultiRecordable::SetAttribute(nostd::string_view key,
+                                   const opentelemetry::common::AttributeValue &value) noexcept
 {
   for (auto &recordable : recordables_)
   {
-    recordable.second->SetSpanId(span_id);
+    if (recordable.second)
+    {
+      recordable.second->SetAttribute(key, value);
+    }
   }
 }
 
-void MultiRecordable::SetTraceFlags(opentelemetry::trace::TraceFlags trace_flags) noexcept
+void MultiRecordable::SetResource(const opentelemetry::sdk::resource::Resource &resource) noexcept
 {
   for (auto &recordable : recordables_)
   {
-    recordable.second->SetTraceFlags(trace_flags);
+    if (recordable.second)
+    {
+      recordable.second->SetResource(resource);
+    }
   }
 }
 
@@ -125,14 +161,15 @@ void MultiRecordable::SetInstrumentationScope(
     const opentelemetry::sdk::instrumentationscope::InstrumentationScope
         &instrumentation_scope) noexcept
 {
-  instrumentation_scope_ = &instrumentation_scope;
+  for (auto &recordable : recordables_)
+  {
+    if (recordable.second)
+    {
+      recordable.second->SetInstrumentationScope(instrumentation_scope);
+    }
+  }
 }
 
-const opentelemetry::sdk::instrumentationscope::InstrumentationScope &
-MultiRecordable::GetInstrumentationScope() const noexcept
-{
-  return *instrumentation_scope_;
-}
 }  // namespace logs
 }  // namespace sdk
 
diff --git a/sdk/src/logs/read_write_log_record.cc b/sdk/src/logs/read_write_log_record.cc
new file mode 100644
index 0000000000..e452fa20e9
--- /dev/null
+++ b/sdk/src/logs/read_write_log_record.cc
@@ -0,0 +1,174 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+#ifdef ENABLE_LOGS_PREVIEW
+
+#  include <cstddef>
+#  include <type_traits>
+
+#  include "opentelemetry/sdk/logs/read_write_log_record.h"
+
+OPENTELEMETRY_BEGIN_NAMESPACE
+namespace sdk
+{
+namespace logs
+{
+
+ReadWriteLogRecord::ReadWriteLogRecord()
+    : severity_(opentelemetry::logs::Severity::kInvalid),
+      resource_(nullptr),
+      instrumentation_scope_(nullptr),
+      body_(nostd::string_view()),
+      observed_timestamp_(std::chrono::system_clock::now())
+{}
+
+ReadWriteLogRecord::~ReadWriteLogRecord() {}
+
+void ReadWriteLogRecord::SetTimestamp(opentelemetry::common::SystemTimestamp timestamp) noexcept
+{
+  timestamp_ = timestamp;
+}
+
+opentelemetry::common::SystemTimestamp ReadWriteLogRecord::GetTimestamp() const noexcept
+{
+  return timestamp_;
+}
+
+void ReadWriteLogRecord::SetObservedTimestamp(
+    opentelemetry::common::SystemTimestamp timestamp) noexcept
+{
+  observed_timestamp_ = timestamp;
+}
+
+opentelemetry::common::SystemTimestamp ReadWriteLogRecord::GetObservedTimestamp() const noexcept
+{
+  return observed_timestamp_;
+}
+
+void ReadWriteLogRecord::SetSeverity(opentelemetry::logs::Severity severity) noexcept
+{
+  severity_ = severity;
+}
+
+opentelemetry::logs::Severity ReadWriteLogRecord::GetSeverity() const noexcept
+{
+  return severity_;
+}
+
+void ReadWriteLogRecord::SetBody(const opentelemetry::common::AttributeValue &message) noexcept
+{
+  body_ = message;
+}
+
+const opentelemetry::common::AttributeValue &ReadWriteLogRecord::GetBody() const noexcept
+{
+  return body_;
+}
+
+void ReadWriteLogRecord::SetTraceId(const opentelemetry::trace::TraceId &trace_id) noexcept
+{
+  if (!trace_state_)
+  {
+    trace_state_ = std::unique_ptr<TraceState>(new TraceState());
+  }
+
+  trace_state_->trace_id = trace_id;
+}
+
+const opentelemetry::trace::TraceId &ReadWriteLogRecord::GetTraceId() const noexcept
+{
+  if (trace_state_)
+  {
+    return trace_state_->trace_id;
+  }
+
+  static opentelemetry::trace::TraceId empty;
+  return empty;
+}
+
+void ReadWriteLogRecord::SetSpanId(const opentelemetry::trace::SpanId &span_id) noexcept
+{
+  if (!trace_state_)
+  {
+    trace_state_ = std::unique_ptr<TraceState>(new TraceState());
+  }
+
+  trace_state_->span_id = span_id;
+}
+
+const opentelemetry::trace::SpanId &ReadWriteLogRecord::GetSpanId() const noexcept
+{
+  if (trace_state_)
+  {
+    return trace_state_->span_id;
+  }
+
+  static opentelemetry::trace::SpanId empty;
+  return empty;
+}
+
+void ReadWriteLogRecord::SetTraceFlags(const opentelemetry::trace::TraceFlags &trace_flags) noexcept
+{
+  if (!trace_state_)
+  {
+    trace_state_ = std::unique_ptr<TraceState>(new TraceState());
+  }
+
+  trace_state_->trace_flags = trace_flags;
+}
+
+const opentelemetry::trace::TraceFlags &ReadWriteLogRecord::GetTraceFlags() const noexcept
+{
+  if (trace_state_)
+  {
+    return trace_state_->trace_flags;
+  }
+
+  static opentelemetry::trace::TraceFlags empty;
+  return empty;
+}
+
+void ReadWriteLogRecord::SetAttribute(nostd::string_view key,
+                                      const opentelemetry::common::AttributeValue &value) noexcept
+{
+  attributes_map_[static_cast<std::string>(key)] = value;
+}
+
+const std::unordered_map<std::string, opentelemetry::common::AttributeValue>
+    &ReadWriteLogRecord::GetAttributes() const noexcept
+{
+  return attributes_map_;
+}
+
+const opentelemetry::sdk::resource::Resource &ReadWriteLogRecord::GetResource() const noexcept
+{
+  OPENTELEMETRY_LIKELY_IF(nullptr != resource_) { return *resource_; }
+
+  return GetDefaultResource();
+}
+
+void ReadWriteLogRecord::SetResource(
+    const opentelemetry::sdk::resource::Resource &resource) noexcept
+{
+  resource_ = &resource;
+}
+
+const opentelemetry::sdk::instrumentationscope::InstrumentationScope &
+ReadWriteLogRecord::GetInstrumentationScope() const noexcept
+{
+  OPENTELEMETRY_LIKELY_IF(nullptr != instrumentation_scope_) { return *instrumentation_scope_; }
+
+  return GetDefaultInstrumentationScope();
+}
+
+void ReadWriteLogRecord::SetInstrumentationScope(
+    const opentelemetry::sdk::instrumentationscope::InstrumentationScope
+        &instrumentation_scope) noexcept
+{
+  instrumentation_scope_ = &instrumentation_scope;
+}
+}  // namespace logs
+}  // namespace sdk
+
+OPENTELEMETRY_END_NAMESPACE
+#endif
diff --git a/sdk/src/logs/readable_log_record.cc b/sdk/src/logs/readable_log_record.cc
new file mode 100644
index 0000000000..9ed5ca7ae7
--- /dev/null
+++ b/sdk/src/logs/readable_log_record.cc
@@ -0,0 +1,51 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+#ifdef ENABLE_LOGS_PREVIEW
+
+#  include <cstddef>
+#  include <type_traits>
+
+#  include "opentelemetry/sdk/logs/readable_log_record.h"
+
+OPENTELEMETRY_BEGIN_NAMESPACE
+namespace sdk
+{
+namespace logs
+{
+
+nostd::string_view ReadableLogRecord::GetSeverityText() const noexcept
+{
+  std::size_t severity_index = static_cast<std::size_t>(GetSeverity());
+  if (severity_index >= std::extent<decltype(opentelemetry::logs::SeverityNumToText)>::value)
+  {
+    return opentelemetry::logs::SeverityNumToText[0];
+  }
+
+  return opentelemetry::logs::SeverityNumToText[severity_index];
+}
+
+const opentelemetry::sdk::instrumentationscope::InstrumentationScope &
+ReadableLogRecord::GetDefaultInstrumentationScope() noexcept
+{
+  // FIXME: Use shared default instrumentation scope?
+  static std::unique_ptr<opentelemetry::sdk::instrumentationscope::InstrumentationScope>
+      default_scope = opentelemetry::sdk::instrumentationscope::InstrumentationScope::Create(
+          "otel-cpp", OPENTELEMETRY_SDK_VERSION, "https://opentelemetry.io/schemas/1.15.0");
+  return *default_scope;
+}
+
+const opentelemetry::sdk::resource::Resource &ReadableLogRecord::GetDefaultResource() noexcept
+{
+  static opentelemetry::sdk::resource::Resource default_resource =
+      opentelemetry::sdk::resource::Resource::Create(
+          {}, GetDefaultInstrumentationScope().GetSchemaURL());
+
+  return default_resource;
+}
+
+}  // namespace logs
+}  // namespace sdk
+
+OPENTELEMETRY_END_NAMESPACE
+#endif
diff --git a/sdk/test/logs/batch_log_record_processor_test.cc b/sdk/test/logs/batch_log_record_processor_test.cc
index bbf1fc3016..de3beb66fe 100644
--- a/sdk/test/logs/batch_log_record_processor_test.cc
+++ b/sdk/test/logs/batch_log_record_processor_test.cc
@@ -5,7 +5,7 @@
 
 #  include "opentelemetry/sdk/logs/batch_log_record_processor.h"
 #  include "opentelemetry/sdk/logs/exporter.h"
-#  include "opentelemetry/sdk/logs/log_record.h"
+#  include "opentelemetry/sdk/logs/recordable.h"
 
 #  include <gtest/gtest.h>
 #  include <chrono>
@@ -15,6 +15,53 @@
 using namespace opentelemetry::sdk::logs;
 using namespace opentelemetry::sdk::common;
 
+namespace nostd = opentelemetry::nostd;
+
+class MockLogRecordable final : public opentelemetry::sdk::logs::Recordable
+{
+public:
+  void SetTimestamp(opentelemetry::common::SystemTimestamp) noexcept override {}
+
+  void SetObservedTimestamp(opentelemetry::common::SystemTimestamp) noexcept override {}
+
+  void SetSeverity(opentelemetry::logs::Severity) noexcept override {}
+
+  nostd::string_view GetBody() const noexcept { return body_; }
+
+  void SetBody(const opentelemetry::common::AttributeValue &message) noexcept override
+  {
+    if (nostd::holds_alternative<const char *>(message))
+    {
+      body_ = nostd::get<const char *>(message);
+    }
+    else if (nostd::holds_alternative<nostd::string_view>(message))
+    {
+      body_ = static_cast<std::string>(nostd::get<nostd::string_view>(message));
+    }
+  }
+
+  void SetBody(const std::string &message) noexcept { body_ = message; }
+
+  void SetTraceId(const opentelemetry::trace::TraceId &) noexcept override {}
+
+  void SetSpanId(const opentelemetry::trace::SpanId &) noexcept override {}
+
+  void SetTraceFlags(const opentelemetry::trace::TraceFlags &) noexcept override {}
+
+  void SetAttribute(nostd::string_view,
+                    const opentelemetry::common::AttributeValue &) noexcept override
+  {}
+
+  void SetResource(const opentelemetry::sdk::resource::Resource &) noexcept override {}
+
+  void SetInstrumentationScope(
+      const opentelemetry::sdk::instrumentationscope::InstrumentationScope &) noexcept override
+  {}
+
+private:
+  std::string body_;
+};
+
 /**
  * A sample log exporter
  * for testing the batch log processor
@@ -22,7 +69,7 @@ using namespace opentelemetry::sdk::common;
 class MockLogExporter final : public LogRecordExporter
 {
 public:
-  MockLogExporter(std::shared_ptr<std::vector<std::unique_ptr<LogRecord>>> logs_received,
+  MockLogExporter(std::shared_ptr<std::vector<std::unique_ptr<MockLogRecordable>>> logs_received,
                   std::shared_ptr<std::atomic<bool>> is_shutdown,
                   std::shared_ptr<std::atomic<bool>> is_export_completed,
                   const std::chrono::milliseconds export_delay = std::chrono::milliseconds(0))
@@ -34,7 +81,7 @@ class MockLogExporter final : public LogRecordExporter
 
   std::unique_ptr<Recordable> MakeRecordable() noexcept override
   {
-    return std::unique_ptr<Recordable>(new LogRecord());
+    return std::unique_ptr<Recordable>(new MockLogRecordable());
   }
 
   // Export method stores the logs received into a shared list of record names
@@ -45,13 +92,19 @@ class MockLogExporter final : public LogRecordExporter
 
     for (auto &record : records)
     {
-      auto log = std::unique_ptr<LogRecord>(static_cast<LogRecord *>(record.release()));
+      auto log =
+          std::unique_ptr<MockLogRecordable>(static_cast<MockLogRecordable *>(record.release()));
       if (log != nullptr)
       {
         logs_received_->push_back(std::move(log));
       }
     }
 
+    if (export_delay_ > std::chrono::milliseconds::zero())
+    {
+      std::this_thread::sleep_for(export_delay_);
+    }
+
     *is_export_completed_ = true;
     return ExportResult::kSuccess;
   }
@@ -64,7 +117,7 @@ class MockLogExporter final : public LogRecordExporter
   }
 
 private:
-  std::shared_ptr<std::vector<std::unique_ptr<LogRecord>>> logs_received_;
+  std::shared_ptr<std::vector<std::unique_ptr<MockLogRecordable>>> logs_received_;
   std::shared_ptr<std::atomic<bool>> is_shutdown_;
   std::shared_ptr<std::atomic<bool>> is_export_completed_;
   const std::chrono::milliseconds export_delay_;
@@ -80,7 +133,7 @@ class BatchLogRecordProcessorTest : public testing::Test  // ::testing::Test
   // returns a batch log processor that received a batch of log records, a shared pointer to a
   // is_shutdown flag, and the processor configuration options (default if unspecified)
   std::shared_ptr<LogRecordProcessor> GetMockProcessor(
-      std::shared_ptr<std::vector<std::unique_ptr<LogRecord>>> logs_received,
+      std::shared_ptr<std::vector<std::unique_ptr<MockLogRecordable>>> logs_received,
       std::shared_ptr<std::atomic<bool>> is_shutdown,
       std::shared_ptr<std::atomic<bool>> is_export_completed =
           std::shared_ptr<std::atomic<bool>>(new std::atomic<bool>(false)),
@@ -99,8 +152,8 @@ class BatchLogRecordProcessorTest : public testing::Test  // ::testing::Test
 TEST_F(BatchLogRecordProcessorTest, TestShutdown)
 {
   // initialize a batch log processor with the test exporter
-  std::shared_ptr<std::vector<std::unique_ptr<LogRecord>>> logs_received(
-      new std::vector<std::unique_ptr<LogRecord>>);
+  std::shared_ptr<std::vector<std::unique_ptr<MockLogRecordable>>> logs_received(
+      new std::vector<std::unique_ptr<MockLogRecordable>>);
   std::shared_ptr<std::atomic<bool>> is_shutdown(new std::atomic<bool>(false));
 
   auto batch_processor = GetMockProcessor(logs_received, is_shutdown);
@@ -111,7 +164,7 @@ TEST_F(BatchLogRecordProcessorTest, TestShutdown)
   for (int i = 0; i < num_logs; ++i)
   {
     auto log = batch_processor->MakeRecordable();
-    log->SetBody("Log" + std::to_string(i));
+    static_cast<MockLogRecordable *>(log.get())->SetBody("Log" + std::to_string(i));
     batch_processor->OnEmit(std::move(log));
   }
 
@@ -137,8 +190,8 @@ TEST_F(BatchLogRecordProcessorTest, TestShutdown)
 TEST_F(BatchLogRecordProcessorTest, TestForceFlush)
 {
   std::shared_ptr<std::atomic<bool>> is_shutdown(new std::atomic<bool>(false));
-  std::shared_ptr<std::vector<std::unique_ptr<LogRecord>>> logs_received(
-      new std::vector<std::unique_ptr<LogRecord>>);
+  std::shared_ptr<std::vector<std::unique_ptr<MockLogRecordable>>> logs_received(
+      new std::vector<std::unique_ptr<MockLogRecordable>>);
 
   auto batch_processor = GetMockProcessor(logs_received, is_shutdown);
   const int num_logs   = 2048;
@@ -146,7 +199,7 @@ TEST_F(BatchLogRecordProcessorTest, TestForceFlush)
   for (int i = 0; i < num_logs; ++i)
   {
     auto log = batch_processor->MakeRecordable();
-    log->SetBody("Log" + std::to_string(i));
+    static_cast<MockLogRecordable *>(log.get())->SetBody("Log" + std::to_string(i));
     batch_processor->OnEmit(std::move(log));
   }
 
@@ -162,7 +215,7 @@ TEST_F(BatchLogRecordProcessorTest, TestForceFlush)
   for (int i = 0; i < num_logs; ++i)
   {
     auto log = batch_processor->MakeRecordable();
-    log->SetBody("Log" + std::to_string(i));
+    static_cast<MockLogRecordable *>(log.get())->SetBody("Log" + std::to_string(i));
     batch_processor->OnEmit(std::move(log));
   }
 
@@ -180,8 +233,8 @@ TEST_F(BatchLogRecordProcessorTest, TestManyLogsLoss)
   /* Test that when exporting more than max_queue_size logs, some are most likely lost*/
 
   std::shared_ptr<std::atomic<bool>> is_shutdown(new std::atomic<bool>(false));
-  std::shared_ptr<std::vector<std::unique_ptr<LogRecord>>> logs_received(
-      new std::vector<std::unique_ptr<LogRecord>>);
+  std::shared_ptr<std::vector<std::unique_ptr<MockLogRecordable>>> logs_received(
+      new std::vector<std::unique_ptr<MockLogRecordable>>);
 
   const int max_queue_size = 4096;
 
@@ -191,7 +244,7 @@ TEST_F(BatchLogRecordProcessorTest, TestManyLogsLoss)
   for (int i = 0; i < max_queue_size; ++i)
   {
     auto log = batch_processor->MakeRecordable();
-    log->SetBody("Log" + std::to_string(i));
+    static_cast<MockLogRecordable *>(log.get())->SetBody("Log" + std::to_string(i));
     batch_processor->OnEmit(std::move(log));
   }
 
@@ -206,8 +259,8 @@ TEST_F(BatchLogRecordProcessorTest, TestManyLogsLossLess)
   /* Test that no logs are lost when sending max_queue_size logs */
 
   std::shared_ptr<std::atomic<bool>> is_shutdown(new std::atomic<bool>(false));
-  std::shared_ptr<std::vector<std::unique_ptr<LogRecord>>> logs_received(
-      new std::vector<std::unique_ptr<LogRecord>>);
+  std::shared_ptr<std::vector<std::unique_ptr<MockLogRecordable>>> logs_received(
+      new std::vector<std::unique_ptr<MockLogRecordable>>);
   auto batch_processor = GetMockProcessor(logs_received, is_shutdown);
 
   const int num_logs = 2048;
@@ -215,7 +268,7 @@ TEST_F(BatchLogRecordProcessorTest, TestManyLogsLossLess)
   for (int i = 0; i < num_logs; ++i)
   {
     auto log = batch_processor->MakeRecordable();
-    log->SetBody("Log" + std::to_string(i));
+    static_cast<MockLogRecordable *>(log.get())->SetBody("Log" + std::to_string(i));
     batch_processor->OnEmit(std::move(log));
   }
 
@@ -235,8 +288,8 @@ TEST_F(BatchLogRecordProcessorTest, TestScheduledDelayMillis)
 
   std::shared_ptr<std::atomic<bool>> is_shutdown(new std::atomic<bool>(false));
   std::shared_ptr<std::atomic<bool>> is_export_completed(new std::atomic<bool>(false));
-  std::shared_ptr<std::vector<std::unique_ptr<LogRecord>>> logs_received(
-      new std::vector<std::unique_ptr<LogRecord>>);
+  std::shared_ptr<std::vector<std::unique_ptr<MockLogRecordable>>> logs_received(
+      new std::vector<std::unique_ptr<MockLogRecordable>>);
 
   const std::chrono::milliseconds export_delay(0);
   const std::chrono::milliseconds scheduled_delay_millis(2000);
@@ -248,7 +301,7 @@ TEST_F(BatchLogRecordProcessorTest, TestScheduledDelayMillis)
   for (std::size_t i = 0; i < max_export_batch_size; ++i)
   {
     auto log = batch_processor->MakeRecordable();
-    log->SetBody("Log" + std::to_string(i));
+    static_cast<MockLogRecordable *>(log.get())->SetBody("Log" + std::to_string(i));
     batch_processor->OnEmit(std::move(log));
   }
   // Sleep for scheduled_delay_millis milliseconds
@@ -257,7 +310,7 @@ TEST_F(BatchLogRecordProcessorTest, TestScheduledDelayMillis)
   // small delay to give time to export, which is being performed
   // asynchronously by the worker thread (this thread will not
   // forcibly join() the main thread unless processor's shutdown() is called).
-  std::this_thread::sleep_for(std::chrono::milliseconds(50));
+  std::this_thread::sleep_for(std::chrono::milliseconds(200));
 
   // Logs should be exported by now
   EXPECT_TRUE(is_export_completed->load());
diff --git a/sdk/test/logs/log_record_test.cc b/sdk/test/logs/log_record_test.cc
index 89b07473a3..3ad3acaca5 100644
--- a/sdk/test/logs/log_record_test.cc
+++ b/sdk/test/logs/log_record_test.cc
@@ -3,28 +3,27 @@
 
 #ifdef ENABLE_LOGS_PREVIEW
 
-#  include "opentelemetry/sdk/logs/log_record.h"
 #  include "opentelemetry/nostd/variant.h"
+#  include "opentelemetry/sdk/logs/read_write_log_record.h"
 #  include "opentelemetry/trace/span_id.h"
 #  include "opentelemetry/trace/trace_id.h"
 
 #  include <gtest/gtest.h>
 
-using opentelemetry::sdk::logs::LogRecord;
+using opentelemetry::sdk::logs::ReadWriteLogRecord;
 namespace trace_api = opentelemetry::trace;
 namespace logs_api  = opentelemetry::logs;
 namespace nostd     = opentelemetry::nostd;
 
-// Test what a default LogRecord with no fields set holds
-TEST(LogRecord, GetDefaultValues)
+// Test what a default ReadWriteLogRecord with no fields set holds
+TEST(ReadWriteLogRecord, GetDefaultValues)
 {
   trace_api::TraceId zero_trace_id;
   trace_api::SpanId zero_span_id;
   trace_api::TraceFlags zero_trace_flags;
-  LogRecord record;
+  ReadWriteLogRecord record;
 
   ASSERT_EQ(record.GetSeverity(), logs_api::Severity::kInvalid);
-  ASSERT_EQ(record.GetBody(), "");
   ASSERT_NE(record.GetResource().GetAttributes().size(), 0);
   ASSERT_EQ(record.GetAttributes().size(), 0);
   ASSERT_EQ(record.GetTraceId(), zero_trace_id);
@@ -33,16 +32,16 @@ TEST(LogRecord, GetDefaultValues)
   ASSERT_EQ(record.GetTimestamp().time_since_epoch(), std::chrono::nanoseconds(0));
 }
 
-// Test LogRecord fields are properly set and get
-TEST(LogRecord, SetAndGet)
+// Test ReadWriteLogRecord fields are properly set and get
+TEST(ReadWriteLogRecord, SetAndGet)
 {
   trace_api::TraceId trace_id;
   trace_api::SpanId span_id;
   trace_api::TraceFlags trace_flags;
   opentelemetry::common::SystemTimestamp now(std::chrono::system_clock::now());
 
-  // Set all fields of the LogRecord
-  LogRecord record;
+  // Set most fields of the ReadWriteLogRecord
+  ReadWriteLogRecord record;
   auto resource = opentelemetry::sdk::resource::Resource::Create({{"res1", true}});
   record.SetSeverity(logs_api::Severity::kInvalid);
   record.SetBody("Message");
@@ -55,7 +54,14 @@ TEST(LogRecord, SetAndGet)
 
   // Test that all fields match what was set
   ASSERT_EQ(record.GetSeverity(), logs_api::Severity::kInvalid);
-  ASSERT_EQ(record.GetBody(), "Message");
+  if (nostd::holds_alternative<const char *>(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_TRUE(nostd::get<bool>(record.GetResource().GetAttributes().at("res1")));
   ASSERT_EQ(nostd::get<int64_t>(record.GetAttributes().at("attr1")), 314159);
   ASSERT_EQ(record.GetTraceId(), trace_id);
diff --git a/sdk/test/logs/logger_provider_sdk_test.cc b/sdk/test/logs/logger_provider_sdk_test.cc
index 4f517f8fd4..9339560377 100644
--- a/sdk/test/logs/logger_provider_sdk_test.cc
+++ b/sdk/test/logs/logger_provider_sdk_test.cc
@@ -7,9 +7,9 @@
 #  include "opentelemetry/logs/provider.h"
 #  include "opentelemetry/nostd/shared_ptr.h"
 #  include "opentelemetry/nostd/string_view.h"
-#  include "opentelemetry/sdk/logs/log_record.h"
 #  include "opentelemetry/sdk/logs/logger.h"
 #  include "opentelemetry/sdk/logs/logger_provider.h"
+#  include "opentelemetry/sdk/logs/recordable.h"
 #  include "opentelemetry/sdk/logs/simple_log_record_processor.h"
 
 #  include <gtest/gtest.h>
@@ -79,11 +79,39 @@ TEST(LoggerProviderSDK, LoggerProviderLoggerArguments)
   ASSERT_EQ(sdk_logger2->GetInstrumentationScope(), sdk_logger1->GetInstrumentationScope());
 }
 
+class DummyLogRecordable final : public opentelemetry::sdk::logs::Recordable
+{
+public:
+  void SetTimestamp(opentelemetry::common::SystemTimestamp) noexcept override {}
+
+  void SetObservedTimestamp(opentelemetry::common::SystemTimestamp) noexcept override {}
+
+  void SetSeverity(opentelemetry::logs::Severity) noexcept override {}
+
+  void SetBody(const opentelemetry::common::AttributeValue &) noexcept override {}
+
+  void SetTraceId(const opentelemetry::trace::TraceId &) noexcept override {}
+
+  void SetSpanId(const opentelemetry::trace::SpanId &) noexcept override {}
+
+  void SetTraceFlags(const opentelemetry::trace::TraceFlags &) noexcept override {}
+
+  void SetAttribute(nostd::string_view,
+                    const opentelemetry::common::AttributeValue &) noexcept override
+  {}
+
+  void SetResource(const opentelemetry::sdk::resource::Resource &) noexcept override {}
+
+  void SetInstrumentationScope(
+      const opentelemetry::sdk::instrumentationscope::InstrumentationScope &) noexcept override
+  {}
+};
+
 class DummyProcessor : public LogRecordProcessor
 {
   std::unique_ptr<Recordable> MakeRecordable() noexcept override
   {
-    return std::unique_ptr<Recordable>(new LogRecord);
+    return std::unique_ptr<Recordable>(new DummyLogRecordable());
   }
 
   void OnEmit(std::unique_ptr<Recordable> && /* record */) noexcept override {}
diff --git a/sdk/test/logs/logger_sdk_test.cc b/sdk/test/logs/logger_sdk_test.cc
index 478d874b8e..b6b597a887 100644
--- a/sdk/test/logs/logger_sdk_test.cc
+++ b/sdk/test/logs/logger_sdk_test.cc
@@ -3,13 +3,18 @@
 
 #ifdef ENABLE_LOGS_PREVIEW
 
-#  include "opentelemetry/sdk/logs/log_record.h"
+#  include <string>
+
+#  include "opentelemetry/nostd/string_view.h"
+#  include "opentelemetry/nostd/variant.h"
 #  include "opentelemetry/sdk/logs/logger.h"
+#  include "opentelemetry/sdk/logs/recordable.h"
 
 #  include <gtest/gtest.h>
 
 using namespace opentelemetry::sdk::logs;
 namespace logs_api = opentelemetry::logs;
+namespace nostd    = opentelemetry::nostd;
 
 TEST(LoggerSDK, LogToNullProcessor)
 {
@@ -29,34 +34,87 @@ TEST(LoggerSDK, LogToNullProcessor)
   logger->Debug("Test log");
 }
 
+class MockLogRecordable final : public opentelemetry::sdk::logs::Recordable
+{
+public:
+  void SetTimestamp(opentelemetry::common::SystemTimestamp) noexcept override {}
+
+  void SetObservedTimestamp(opentelemetry::common::SystemTimestamp) noexcept override {}
+
+  opentelemetry::logs::Severity GetSeverity() const noexcept { return severity_; }
+
+  void SetSeverity(opentelemetry::logs::Severity severity) noexcept override
+  {
+    severity_ = severity;
+  }
+
+  nostd::string_view GetBody() const noexcept { return body_; }
+
+  void SetBody(const opentelemetry::common::AttributeValue &message) noexcept override
+  {
+    if (nostd::holds_alternative<const char *>(message))
+    {
+      body_ = nostd::get<const char *>(message);
+    }
+    else if (nostd::holds_alternative<nostd::string_view>(message))
+    {
+      body_ = static_cast<std::string>(nostd::get<nostd::string_view>(message));
+    }
+  }
+
+  void SetBody(const std::string &message) noexcept { body_ = message; }
+
+  void SetTraceId(const opentelemetry::trace::TraceId &) noexcept override {}
+
+  void SetSpanId(const opentelemetry::trace::SpanId &) noexcept override {}
+
+  void SetTraceFlags(const opentelemetry::trace::TraceFlags &) noexcept override {}
+
+  void SetAttribute(nostd::string_view,
+                    const opentelemetry::common::AttributeValue &) noexcept override
+  {}
+
+  void SetResource(const opentelemetry::sdk::resource::Resource &) noexcept override {}
+
+  void SetInstrumentationScope(
+      const opentelemetry::sdk::instrumentationscope::InstrumentationScope &) noexcept override
+  {}
+
+private:
+  opentelemetry::logs::Severity severity_ = opentelemetry::logs::Severity::kInvalid;
+  std::string body_;
+};
+
 class MockProcessor final : public LogRecordProcessor
 {
 private:
-  std::shared_ptr<LogRecord> record_received_;
+  std::shared_ptr<MockLogRecordable> record_received_;
 
 public:
   // A processor used for testing that keeps a track of the recordable it received
-  explicit MockProcessor(std::shared_ptr<LogRecord> record_received) noexcept
+  explicit MockProcessor(std::shared_ptr<MockLogRecordable> record_received) noexcept
       : record_received_(record_received)
   {}
 
-  std::unique_ptr<Recordable> MakeRecordable() noexcept override
+  std::unique_ptr<opentelemetry::sdk::logs::Recordable> MakeRecordable() noexcept override
   {
-    return std::unique_ptr<Recordable>(new LogRecord);
+    return std::unique_ptr<opentelemetry::sdk::logs::Recordable>(new MockLogRecordable());
   }
 
   // OnEmit stores the record it receives into the shared_ptr recordable passed into its
   // constructor
-  void OnEmit(std::unique_ptr<Recordable> &&record) noexcept override
+  void OnEmit(std::unique_ptr<opentelemetry::sdk::logs::Recordable> &&record) noexcept override
   {
-    // Cast the recordable received into a concrete LogRecord type
-    auto copy = std::shared_ptr<LogRecord>(static_cast<LogRecord *>(record.release()));
+    // Cast the recordable received into a concrete MockLogRecordable type
+    auto copy =
+        std::shared_ptr<MockLogRecordable>(static_cast<MockLogRecordable *>(record.release()));
 
     // Copy over the received log record's severity, name, and body fields over to the recordable
     // passed in the constructor
     record_received_->SetSeverity(copy->GetSeverity());
     record_received_->SetBody(copy->GetBody());
   }
+
   bool ForceFlush(std::chrono::microseconds /* timeout */) noexcept override { return true; }
   bool Shutdown(std::chrono::microseconds /* timeout */) noexcept override { return true; }
 };
@@ -80,8 +138,9 @@ TEST(LoggerSDK, LogToAProcessor)
   ASSERT_EQ(sdk_logger->GetInstrumentationScope().GetVersion(), "");
   ASSERT_EQ(sdk_logger->GetInstrumentationScope().GetSchemaURL(), schema_url);
   // Set a processor for the LoggerProvider
-  auto shared_recordable = std::shared_ptr<LogRecord>(new LogRecord());
-  lp->AddProcessor(std::unique_ptr<LogRecordProcessor>(new MockProcessor(shared_recordable)));
+  auto shared_recordable = std::shared_ptr<MockLogRecordable>(new MockLogRecordable());
+  lp->AddProcessor(std::unique_ptr<opentelemetry::sdk::logs::LogRecordProcessor>(
+      new MockProcessor(shared_recordable)));
 
   // Check that the recordable created by the Log() statement is set properly
   logger->Log(logs_api::Severity::kWarn, "Log Message");
diff --git a/sdk/test/logs/simple_log_record_processor_test.cc b/sdk/test/logs/simple_log_record_processor_test.cc
index 47b16eab7e..275991dc10 100644
--- a/sdk/test/logs/simple_log_record_processor_test.cc
+++ b/sdk/test/logs/simple_log_record_processor_test.cc
@@ -6,7 +6,7 @@
 #  include "opentelemetry/sdk/logs/simple_log_record_processor.h"
 #  include "opentelemetry/nostd/span.h"
 #  include "opentelemetry/sdk/logs/exporter.h"
-#  include "opentelemetry/sdk/logs/log_record.h"
+#  include "opentelemetry/sdk/logs/recordable.h"
 
 #  include <gtest/gtest.h>
 
@@ -17,6 +17,51 @@ using namespace opentelemetry::sdk::logs;
 using namespace opentelemetry::sdk::common;
 namespace nostd = opentelemetry::nostd;
 
+class TestLogRecordable final : public opentelemetry::sdk::logs::Recordable
+{
+public:
+  void SetTimestamp(opentelemetry::common::SystemTimestamp) noexcept override {}
+
+  void SetObservedTimestamp(opentelemetry::common::SystemTimestamp) noexcept override {}
+
+  void SetSeverity(opentelemetry::logs::Severity) noexcept override {}
+
+  nostd::string_view GetBody() const noexcept { return body_; }
+
+  void SetBody(const opentelemetry::common::AttributeValue &message) noexcept override
+  {
+    if (nostd::holds_alternative<const char *>(message))
+    {
+      body_ = nostd::get<const char *>(message);
+    }
+    else if (nostd::holds_alternative<nostd::string_view>(message))
+    {
+      body_ = static_cast<std::string>(nostd::get<nostd::string_view>(message));
+    }
+  }
+
+  void SetBody(const char *message) noexcept { body_ = message; }
+
+  void SetTraceId(const opentelemetry::trace::TraceId &) noexcept override {}
+
+  void SetSpanId(const opentelemetry::trace::SpanId &) noexcept override {}
+
+  void SetTraceFlags(const opentelemetry::trace::TraceFlags &) noexcept override {}
+
+  void SetAttribute(nostd::string_view,
+                    const opentelemetry::common::AttributeValue &) noexcept override
+  {}
+
+  void SetResource(const opentelemetry::sdk::resource::Resource &) noexcept override {}
+
+  void SetInstrumentationScope(
+      const opentelemetry::sdk::instrumentationscope::InstrumentationScope &) noexcept override
+  {}
+
+private:
+  std::string body_;
+};
+
 /*
  * A test exporter that can return a vector of all the records it has received,
  * and keep track of the number of times its Shutdown() function was called.
@@ -25,7 +70,7 @@ class TestExporter final : public LogRecordExporter
 {
 public:
   TestExporter(int *shutdown_counter,
-               std::shared_ptr<std::vector<std::unique_ptr<LogRecord>>> logs_received,
+               std::shared_ptr<std::vector<std::unique_ptr<TestLogRecordable>>> logs_received,
                size_t *batch_size_received)
       : shutdown_counter_(shutdown_counter),
         logs_received_(logs_received),
@@ -34,7 +79,7 @@ class TestExporter final : public LogRecordExporter
 
   std::unique_ptr<Recordable> MakeRecordable() noexcept override
   {
-    return std::unique_ptr<Recordable>(new LogRecord());
+    return std::unique_ptr<Recordable>(new TestLogRecordable());
   }
 
   // Stores the names of the log records this exporter receives to an internal list
@@ -43,7 +88,8 @@ class TestExporter final : public LogRecordExporter
     *batch_size_received = records.size();
     for (auto &record : records)
     {
-      auto log_record = std::unique_ptr<LogRecord>(static_cast<LogRecord *>(record.release()));
+      auto log_record =
+          std::unique_ptr<TestLogRecordable>(static_cast<TestLogRecordable *>(record.release()));
 
       if (log_record != nullptr)
       {
@@ -62,7 +108,7 @@ class TestExporter final : public LogRecordExporter
 
 private:
   int *shutdown_counter_;
-  std::shared_ptr<std::vector<std::unique_ptr<LogRecord>>> logs_received_;
+  std::shared_ptr<std::vector<std::unique_ptr<TestLogRecordable>>> logs_received_;
   size_t *batch_size_received;
 };
 
@@ -71,8 +117,8 @@ class TestExporter final : public LogRecordExporter
 TEST(SimpleLogRecordProcessorTest, SendReceivedLogsToExporter)
 {
   // Create a simple processor with a TestExporter attached
-  std::shared_ptr<std::vector<std::unique_ptr<LogRecord>>> logs_received(
-      new std::vector<std::unique_ptr<LogRecord>>);
+  std::shared_ptr<std::vector<std::unique_ptr<TestLogRecordable>>> logs_received(
+      new std::vector<std::unique_ptr<TestLogRecordable>>);
   size_t batch_size_received = 0;
 
   std::unique_ptr<TestExporter> exporter(
@@ -85,7 +131,7 @@ TEST(SimpleLogRecordProcessorTest, SendReceivedLogsToExporter)
   for (int i = 0; i < num_logs; i++)
   {
     auto recordable = processor.MakeRecordable();
-    recordable->SetBody("Log Body");
+    static_cast<TestLogRecordable *>(recordable.get())->SetBody("Log Body");
     processor.OnEmit(std::move(recordable));
 
     // Verify that the batch of 1 log record sent by processor matches what exporter received
@@ -129,7 +175,7 @@ class FailShutDownExporter final : public LogRecordExporter
 
   std::unique_ptr<Recordable> MakeRecordable() noexcept override
   {
-    return std::unique_ptr<Recordable>(new LogRecord());
+    return std::unique_ptr<Recordable>(new TestLogRecordable());
   }
 
   ExportResult Export(