diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md
index dbea72b5ad0..90f64dd9c0a 100644
--- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md
+++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md
@@ -12,6 +12,13 @@
   and `OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT`.
   ([#4887](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4887))
 
+* Added ability to export attributes corresponding to `LogRecord.Exception` i.e.
+`exception.type`, `exception.message` and `exception.stacktrace`. These
+attributes will be exported when
+`OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES` environment
+variable will be set to `true`.
+([#4892](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4892))
+
 ## 1.6.0
 
 Released 2023-Sep-05
diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExperimentalOptions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExperimentalOptions.cs
new file mode 100644
index 00000000000..2734ad8a158
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExperimentalOptions.cs
@@ -0,0 +1,45 @@
+// <copyright file="ExperimentalOptions.cs" company="OpenTelemetry Authors">
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// </copyright>
+
+#nullable enable
+
+using Microsoft.Extensions.Configuration;
+using OpenTelemetry.Internal;
+
+namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation;
+
+internal sealed class ExperimentalOptions
+{
+    public const string EMITLOGEXCEPTIONATTRIBUTES = "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES";
+
+    public ExperimentalOptions()
+        : this(new ConfigurationBuilder().AddEnvironmentVariables().Build())
+    {
+    }
+
+    public ExperimentalOptions(IConfiguration configuration)
+    {
+        if (configuration.TryGetBoolValue(EMITLOGEXCEPTIONATTRIBUTES, out var emitLogExceptionAttributes))
+        {
+            this.EmitLogExceptionAttributes = emitLogExceptionAttributes;
+        }
+    }
+
+    /// <summary>
+    /// Gets or sets a value indicating whether log exception attributes should be exported.
+    /// </summary>
+    public bool EmitLogExceptionAttributes { get; set; } = false;
+}
diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/LogRecordExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpLogRecordTransformer.cs
similarity index 78%
rename from src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/LogRecordExtensions.cs
rename to src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpLogRecordTransformer.cs
index 028a02b5126..3a7be7f04c8 100644
--- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/LogRecordExtensions.cs
+++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpLogRecordTransformer.cs
@@ -1,4 +1,4 @@
-// <copyright file="LogRecordExtensions.cs" company="OpenTelemetry Authors">
+// <copyright file="OtlpLogRecordTransformer.cs" company="OpenTelemetry Authors">
 // Copyright The OpenTelemetry Authors
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,7 +16,9 @@
 
 using System.Runtime.CompilerServices;
 using Google.Protobuf;
+using OpenTelemetry.Internal;
 using OpenTelemetry.Logs;
+using OpenTelemetry.Trace;
 using OtlpCollector = OpenTelemetry.Proto.Collector.Logs.V1;
 using OtlpCommon = OpenTelemetry.Proto.Common.V1;
 using OtlpLogs = OpenTelemetry.Proto.Logs.V1;
@@ -24,14 +26,23 @@
 
 namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation;
 
-internal static class LogRecordExtensions
+internal sealed class OtlpLogRecordTransformer
 {
-    internal static void AddBatch(
-        this OtlpCollector.ExportLogsServiceRequest request,
-        SdkLimitOptions sdkLimitOptions,
+    private readonly SdkLimitOptions sdkLimitOptions;
+    private readonly ExperimentalOptions experimentalOptions;
+
+    public OtlpLogRecordTransformer(SdkLimitOptions sdkLimitOptions, ExperimentalOptions experimentalOptions)
+    {
+        this.sdkLimitOptions = sdkLimitOptions;
+        this.experimentalOptions = experimentalOptions;
+    }
+
+    internal OtlpCollector.ExportLogsServiceRequest BuildExportRequest(
         OtlpResource.Resource processResource,
         in Batch<LogRecord> logRecordBatch)
     {
+        var request = new OtlpCollector.ExportLogsServiceRequest();
+
         var resourceLogs = new OtlpLogs.ResourceLogs
         {
             Resource = processResource,
@@ -43,16 +54,18 @@ internal static void AddBatch(
 
         foreach (var logRecord in logRecordBatch)
         {
-            var otlpLogRecord = logRecord.ToOtlpLog(sdkLimitOptions);
+            var otlpLogRecord = this.ToOtlpLog(logRecord);
             if (otlpLogRecord != null)
             {
                 scopeLogs.LogRecords.Add(otlpLogRecord);
             }
         }
+
+        return request;
     }
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    internal static OtlpLogs.LogRecord ToOtlpLog(this LogRecord logRecord, SdkLimitOptions sdkLimitOptions)
+    internal OtlpLogs.LogRecord ToOtlpLog(LogRecord logRecord)
     {
         OtlpLogs.LogRecord otlpLogRecord = null;
 
@@ -75,8 +88,8 @@ internal static OtlpLogs.LogRecord ToOtlpLog(this LogRecord logRecord, SdkLimitO
                 otlpLogRecord.SeverityText = logRecord.Severity.Value.ToShortName();
             }
 
-            var attributeValueLengthLimit = sdkLimitOptions.LogRecordAttributeValueLengthLimit;
-            var attributeCountLimit = sdkLimitOptions.LogRecordAttributeCountLimit ?? int.MaxValue;
+            var attributeValueLengthLimit = this.sdkLimitOptions.LogRecordAttributeValueLengthLimit;
+            var attributeCountLimit = this.sdkLimitOptions.LogRecordAttributeCountLimit ?? int.MaxValue;
 
             /*
             // Removing this temporarily for stable release
@@ -104,14 +117,14 @@ internal static OtlpLogs.LogRecord ToOtlpLog(this LogRecord logRecord, SdkLimitO
             {
                 otlpLogRecord.AddStringAttribute(nameof(logRecord.EventId.Name), logRecord.EventId.Name, attributeValueLengthLimit, attributeCountLimit);
             }
+            */
 
-            if (logRecord.Exception != null)
+            if (this.experimentalOptions.EmitLogExceptionAttributes && logRecord.Exception != null)
             {
-                otlpLogRecord.AddStringAttribute(SemanticConventions.AttributeExceptionType, logRecord.Exception.GetType().Name, attributeValueLengthLimit, attributeCountLimit);
-                otlpLogRecord.AddStringAttribute(SemanticConventions.AttributeExceptionMessage, logRecord.Exception.Message, attributeValueLengthLimit, attributeCountLimit);
-                otlpLogRecord.AddStringAttribute(SemanticConventions.AttributeExceptionStacktrace, logRecord.Exception.ToInvariantString(), attributeValueLengthLimit, attributeCountLimit);
+                AddStringAttribute(otlpLogRecord, SemanticConventions.AttributeExceptionType, logRecord.Exception.GetType().Name, attributeValueLengthLimit, attributeCountLimit);
+                AddStringAttribute(otlpLogRecord, SemanticConventions.AttributeExceptionMessage, logRecord.Exception.Message, attributeValueLengthLimit, attributeCountLimit);
+                AddStringAttribute(otlpLogRecord, SemanticConventions.AttributeExceptionStacktrace, logRecord.Exception.ToInvariantString(), attributeValueLengthLimit, attributeCountLimit);
             }
-            */
 
             bool bodyPopulatedFromFormattedMessage = false;
             if (logRecord.FormattedMessage != null)
@@ -133,7 +146,7 @@ internal static OtlpLogs.LogRecord ToOtlpLog(this LogRecord logRecord, SdkLimitO
                     }
                     else if (OtlpKeyValueTransformer.Instance.TryTransformTag(attribute, out var result, attributeValueLengthLimit))
                     {
-                        otlpLogRecord.AddAttribute(result, attributeCountLimit);
+                        AddAttribute(otlpLogRecord, result, attributeCountLimit);
                     }
                 }
             }
@@ -183,7 +196,7 @@ void ProcessScope(LogRecordScope scope, OtlpLogs.LogRecord otlpLog)
                     {
                         if (OtlpKeyValueTransformer.Instance.TryTransformTag(scopeItem, out var result, attributeValueLengthLimit))
                         {
-                            otlpLog.AddAttribute(result, attributeCountLimit);
+                            AddAttribute(otlpLog, result, attributeCountLimit);
                         }
                     }
                 }
@@ -198,7 +211,7 @@ void ProcessScope(LogRecordScope scope, OtlpLogs.LogRecord otlpLog)
     }
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    private static void AddAttribute(this OtlpLogs.LogRecord logRecord, OtlpCommon.KeyValue attribute, int maxAttributeCount)
+    private static void AddAttribute(OtlpLogs.LogRecord logRecord, OtlpCommon.KeyValue attribute, int maxAttributeCount)
     {
         if (logRecord.Attributes.Count < maxAttributeCount)
         {
@@ -211,22 +224,22 @@ private static void AddAttribute(this OtlpLogs.LogRecord logRecord, OtlpCommon.K
     }
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    private static void AddStringAttribute(this OtlpLogs.LogRecord logRecord, string key, string value, int? maxValueLength, int maxAttributeCount)
+    private static void AddStringAttribute(OtlpLogs.LogRecord logRecord, string key, string value, int? maxValueLength, int maxAttributeCount)
     {
         var attributeItem = new KeyValuePair<string, object>(key, value);
         if (OtlpKeyValueTransformer.Instance.TryTransformTag(attributeItem, out var result, maxValueLength))
         {
-            logRecord.AddAttribute(result, maxAttributeCount);
+            AddAttribute(logRecord, result, maxAttributeCount);
         }
     }
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    private static void AddIntAttribute(this OtlpLogs.LogRecord logRecord, string key, int value, int maxAttributeCount)
+    private static void AddIntAttribute(OtlpLogs.LogRecord logRecord, string key, int value, int maxAttributeCount)
     {
         var attributeItem = new KeyValuePair<string, object>(key, value);
         if (OtlpKeyValueTransformer.Instance.TryTransformTag(attributeItem, out var result))
         {
-            logRecord.AddAttribute(result, maxAttributeCount);
+            AddAttribute(logRecord, result, maxAttributeCount);
         }
     }
 
diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporter.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporter.cs
index 3a1b4551f98..715ec0de263 100644
--- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporter.cs
+++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporter.cs
@@ -30,8 +30,8 @@ namespace OpenTelemetry.Exporter;
 /// </summary>
 internal sealed class OtlpLogExporter : BaseExporter<LogRecord>
 {
-    private readonly SdkLimitOptions sdkLimitOptions;
     private readonly IExportClient<OtlpCollector.ExportLogsServiceRequest> exportClient;
+    private readonly OtlpLogRecordTransformer otlpLogRecordTransformer;
 
     private OtlpResource.Resource processResource;
 
@@ -58,8 +58,6 @@ internal OtlpLogExporter(
         Debug.Assert(exporterOptions != null, "exporterOptions was null");
         Debug.Assert(sdkLimitOptions != null, "sdkLimitOptions was null");
 
-        this.sdkLimitOptions = sdkLimitOptions;
-
         // Each of the Otlp exporters: Traces, Metrics, and Logs set the same value for `OtlpKeyValueTransformer.LogUnsupportedAttributeType`
         // and `ConfigurationExtensions.LogInvalidEnvironmentVariable` so it should be fine even if these exporters are used together.
         OtlpKeyValueTransformer.LogUnsupportedAttributeType = (string tagValueType, string tagKey) =>
@@ -80,6 +78,8 @@ internal OtlpLogExporter(
         {
             this.exportClient = exporterOptions.GetLogExportClient();
         }
+
+        this.otlpLogRecordTransformer = new OtlpLogRecordTransformer(sdkLimitOptions, new());
     }
 
     internal OtlpResource.Resource ProcessResource => this.processResource ??= this.ParentProvider.GetResource().ToOtlpResource();
@@ -90,11 +90,9 @@ public override ExportResult Export(in Batch<LogRecord> logRecordBatch)
         // Prevents the exporter's gRPC and HTTP operations from being instrumented.
         using var scope = SuppressInstrumentationScope.Begin();
 
-        var request = new OtlpCollector.ExportLogsServiceRequest();
-
         try
         {
-            request.AddBatch(this.sdkLimitOptions, this.ProcessResource, logRecordBatch);
+            var request = this.otlpLogRecordTransformer.BuildExportRequest(this.ProcessResource, logRecordBatch);
 
             if (!this.exportClient.SendExportRequest(request))
             {
diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md
index 183df4498f4..2f53e327ecc 100644
--- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md
+++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md
@@ -218,6 +218,17 @@ values of the log record limits
 * `OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT`
 * `OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT`
 
+## Environment Variables for Experimental Features
+
+### Otlp Log Exporter
+
+* `OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES`
+
+When set to `true`, it enables export of attributes corresponding to
+`LogRecord.Exception`. The attributes `exception.type`, `exception.message` and
+`exception.stacktrace` are defined in
+[specification](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/exceptions/exceptions-logs.md#attributes).
+
 ## Configure HttpClient
 
 The `HttpClientFactory` option is provided on `OtlpExporterOptions` for users
diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs
index 7a32a4a34eb..0c2e6c06f9b 100644
--- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs
+++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs
@@ -17,12 +17,14 @@
 using System.Collections.ObjectModel;
 using System.Diagnostics;
 using System.Reflection;
+using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Hosting;
 using Microsoft.Extensions.Logging;
 using Moq;
 using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation;
 using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient;
+using OpenTelemetry.Internal;
 using OpenTelemetry.Logs;
 using OpenTelemetry.Tests;
 using OpenTelemetry.Trace;
@@ -178,8 +180,10 @@ public void OtlpLogRecordTestWhenStateValuesArePopulated()
 
         Assert.Single(logRecords);
 
+        var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new());
+
         var logRecord = logRecords[0];
-        var otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord);
 
         Assert.NotNull(otlpLogRecord);
         Assert.Equal("Hello from tomato 2.99.", otlpLogRecord.Body.StringValue);
@@ -223,7 +227,7 @@ public void CheckToOtlpLogRecordLoggerCategory()
         Assert.Single(logRecords);
 
         var logRecord = logRecords[0];
-        var otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        var otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions, new());
         Assert.NotNull(otlpLogRecord);
         Assert.Single(otlpLogRecord.Attributes);
 
@@ -237,7 +241,7 @@ public void CheckToOtlpLogRecordLoggerCategory()
         Assert.Single(logRecords);
 
         logRecord = logRecords[0];
-        otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions, new());
         Assert.NotNull(otlpLogRecord);
         Assert.Empty(otlpLogRecord.Attributes);
     }
@@ -261,7 +265,7 @@ public void CheckToOtlpLogRecordEventId()
         Assert.Single(logRecords);
 
         var logRecord = logRecords[0];
-        var otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        var otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions, new());
 
         Assert.NotNull(otlpLogRecord);
         Assert.Equal("Hello from tomato 2.99.", otlpLogRecord.Body.StringValue);
@@ -278,7 +282,7 @@ public void CheckToOtlpLogRecordEventId()
         Assert.Single(logRecords);
 
         logRecord = logRecords[0];
-        otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions, new());
         Assert.NotNull(otlpLogRecord);
         Assert.Equal("Hello from tomato 2.99.", otlpLogRecord.Body.StringValue);
 
@@ -306,8 +310,10 @@ public void CheckToOtlpLogRecordTimestamps()
 
         var logger = loggerFactory.CreateLogger("OtlpLogExporterTests");
         logger.LogInformation("Log message");
+        var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new());
+
         var logRecord = logRecords[0];
-        var otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord);
 
         Assert.True(otlpLogRecord.TimeUnixNano > 0);
         Assert.True(otlpLogRecord.ObservedTimeUnixNano > 0);
@@ -327,8 +333,11 @@ public void CheckToOtlpLogRecordTraceIdSpanIdFlagWithNoActivity()
 
         var logger = loggerFactory.CreateLogger("OtlpLogExporterTests");
         logger.LogInformation("Log when there is no activity.");
+
+        var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new());
+
         var logRecord = logRecords[0];
-        var otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord);
 
         Assert.Null(Activity.Current);
         Assert.True(otlpLogRecord.TraceId.IsEmpty);
@@ -360,8 +369,10 @@ public void CheckToOtlpLogRecordSpanIdTraceIdAndFlag()
             expectedSpanId = activity.SpanId;
         }
 
+        var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new());
+
         var logRecord = logRecords[0];
-        var otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord);
 
         Assert.Equal(expectedTraceId.ToString(), ActivityTraceId.CreateFromBytes(otlpLogRecord.TraceId.ToByteArray()).ToString());
         Assert.Equal(expectedSpanId.ToString(), ActivitySpanId.CreateFromBytes(otlpLogRecord.SpanId.ToByteArray()).ToString());
@@ -392,8 +403,10 @@ public void CheckToOtlpLogRecordSeverityLevelAndText(LogLevel logLevel)
         logger.Log(logLevel, "Hello from {name} {price}.", "tomato", 2.99);
         Assert.Single(logRecords);
 
+        var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new());
+
         var logRecord = logRecords[0];
-        var otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord);
 
         Assert.NotNull(otlpLogRecord);
 #pragma warning disable CS0618 // Type or member is obsolete
@@ -445,8 +458,10 @@ public void CheckToOtlpLogRecordBodyIsPopulated(bool includeFormattedMessage)
         logger.LogInformation("OpenTelemetry {Greeting} {Subject}!", "Hello", "World");
         Assert.Single(logRecords);
 
+        var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new());
+
         var logRecord = logRecords[0];
-        var otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord);
 
         Assert.NotNull(otlpLogRecord);
         if (includeFormattedMessage)
@@ -465,7 +480,7 @@ public void CheckToOtlpLogRecordBodyIsPopulated(bool includeFormattedMessage)
         Assert.Single(logRecords);
 
         logRecord = logRecords[0];
-        otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord);
 
         Assert.NotNull(otlpLogRecord);
 
@@ -481,7 +496,7 @@ public void CheckToOtlpLogRecordBodyIsPopulated(bool includeFormattedMessage)
         Assert.Single(logRecords);
 
         logRecord = logRecords[0];
-        otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord);
 
         Assert.NotNull(otlpLogRecord);
 
@@ -489,9 +504,10 @@ public void CheckToOtlpLogRecordBodyIsPopulated(bool includeFormattedMessage)
         Assert.Equal("state", otlpLogRecord.Body.StringValue);
     }
 
-    /*
-    [Fact]
-    public void CheckToOtlpLogRecordExceptionAttributes()
+    [Theory]
+    [InlineData("true")]
+    [InlineData("false")]
+    public void CheckToOtlpLogRecordExceptionAttributes(string emitExceptionAttributes)
     {
         var logRecords = new List<LogRecord>();
         using var loggerFactory = LoggerFactory.Create(builder =>
@@ -507,20 +523,40 @@ public void CheckToOtlpLogRecordExceptionAttributes()
 
         var logRecord = logRecords[0];
         var loggedException = logRecord.Exception;
-        var otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        var configuration = new ConfigurationBuilder()
+            .AddInMemoryCollection(new Dictionary<string, string> { [ExperimentalOptions.EMITLOGEXCEPTIONATTRIBUTES] = emitExceptionAttributes })
+            .Build();
+
+        var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new(configuration));
+
+        var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord);
 
         Assert.NotNull(otlpLogRecord);
         var otlpLogRecordAttributes = otlpLogRecord.Attributes.ToString();
-        Assert.Contains(SemanticConventions.AttributeExceptionType, otlpLogRecordAttributes);
-        Assert.Contains(logRecord.Exception.GetType().Name, otlpLogRecordAttributes);
 
-        Assert.Contains(SemanticConventions.AttributeExceptionMessage, otlpLogRecordAttributes);
-        Assert.Contains(logRecord.Exception.Message, otlpLogRecordAttributes);
+        if (emitExceptionAttributes == "true")
+        {
+            Assert.Contains(SemanticConventions.AttributeExceptionType, otlpLogRecordAttributes);
+            Assert.Contains(logRecord.Exception.GetType().Name, otlpLogRecordAttributes);
+
+            Assert.Contains(SemanticConventions.AttributeExceptionMessage, otlpLogRecordAttributes);
+            Assert.Contains(logRecord.Exception.Message, otlpLogRecordAttributes);
 
-        Assert.Contains(SemanticConventions.AttributeExceptionStacktrace, otlpLogRecordAttributes);
-        Assert.Contains(logRecord.Exception.ToInvariantString(), otlpLogRecordAttributes);
+            Assert.Contains(SemanticConventions.AttributeExceptionStacktrace, otlpLogRecordAttributes);
+            Assert.Contains(logRecord.Exception.ToInvariantString(), otlpLogRecordAttributes);
+        }
+        else
+        {
+            Assert.DoesNotContain(SemanticConventions.AttributeExceptionType, otlpLogRecordAttributes);
+            Assert.DoesNotContain(logRecord.Exception.GetType().Name, otlpLogRecordAttributes);
+
+            Assert.DoesNotContain(SemanticConventions.AttributeExceptionMessage, otlpLogRecordAttributes);
+            Assert.DoesNotContain(logRecord.Exception.Message, otlpLogRecordAttributes);
+
+            Assert.DoesNotContain(SemanticConventions.AttributeExceptionStacktrace, otlpLogRecordAttributes);
+            Assert.DoesNotContain(logRecord.Exception.ToInvariantString(), otlpLogRecordAttributes);
+        }
     }
-    */
 
     [Fact]
     public void CheckToOtlpLogRecordRespectsAttributeLimits()
@@ -544,8 +580,10 @@ public void CheckToOtlpLogRecordRespectsAttributeLimits()
         var logger = loggerFactory.CreateLogger(string.Empty);
         logger.LogInformation("OpenTelemetry {AttributeOne} {AttributeTwo} {AttributeThree}!", "I'm an attribute", "I too am an attribute", "I get dropped :(");
 
+        var otlpLogRecordTransformer = new OtlpLogRecordTransformer(sdkLimitOptions, new());
+
         var logRecord = logRecords[0];
-        var otlpLogRecord = logRecord.ToOtlpLog(sdkLimitOptions);
+        var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord);
 
         Assert.NotNull(otlpLogRecord);
         Assert.Equal(1u, otlpLogRecord.DroppedAttributesCount);
@@ -657,7 +695,8 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsFalse_DoesNotContainScopeAttribu
 
         // Assert.
         var logRecord = logRecords.Single();
-        var otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new());
+        var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord);
         var actualScope = TryGetAttribute(otlpLogRecord, expectedScopeKey);
         Assert.Null(actualScope);
     }
@@ -692,7 +731,8 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeStrin
 
         // Assert.
         var logRecord = logRecords.Single();
-        var otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new());
+        var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord);
         Assert.Single(otlpLogRecord.Attributes);
         var actualScope = TryGetAttribute(otlpLogRecord, scopeKey);
         Assert.NotNull(actualScope);
@@ -731,7 +771,8 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeBoolV
 
         // Assert.
         var logRecord = logRecords.Single();
-        var otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new());
+        var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord);
         Assert.Single(otlpLogRecord.Attributes);
         var actualScope = TryGetAttribute(otlpLogRecord, scopeKey);
         Assert.NotNull(actualScope);
@@ -782,7 +823,8 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeIntVa
 
         // Assert.
         var logRecord = logRecords.Single();
-        var otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new());
+        var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord);
         Assert.Single(otlpLogRecord.Attributes);
         var actualScope = TryGetAttribute(otlpLogRecord, scopeKey);
         Assert.NotNull(actualScope);
@@ -821,7 +863,8 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeDoubl
 
         // Assert.
         var logRecord = logRecords.Single();
-        var otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new());
+        var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord);
         Assert.Single(otlpLogRecord.Attributes);
         var actualScope = TryGetAttribute(otlpLogRecord, scopeKey);
         Assert.NotNull(actualScope);
@@ -860,7 +903,8 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeDoubl
 
         // Assert.
         var logRecord = logRecords.Single();
-        var otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new());
+        var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord);
         Assert.Single(otlpLogRecord.Attributes);
         var actualScope = TryGetAttribute(otlpLogRecord, scopeKey);
         Assert.NotNull(actualScope);
@@ -893,7 +937,8 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeStateIsOfTypeString
 
         // Assert.
         var logRecord = logRecords.Single();
-        var otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new());
+        var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord);
         Assert.NotNull(otlpLogRecord);
         Assert.Empty(otlpLogRecord.Attributes);
     }
@@ -928,7 +973,8 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeStateIsOfPrimitiveT
 
         // Assert.
         var logRecord = logRecords.Single();
-        var otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new());
+        var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord);
         Assert.NotNull(otlpLogRecord);
         Assert.Empty(otlpLogRecord.Attributes);
     }
@@ -960,7 +1006,8 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeStateIsOfDictionary
 
         // Assert.
         var logRecord = logRecords.Single();
-        var otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new());
+        var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord);
         Assert.Single(otlpLogRecord.Attributes);
         var actualScope = TryGetAttribute(otlpLogRecord, scopeKey);
         Assert.NotNull(actualScope);
@@ -999,7 +1046,8 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeStateIsOfEnumerable
 
         // Assert.
         var logRecord = logRecords.Single();
-        var otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new());
+        var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord);
         Assert.Single(otlpLogRecord.Attributes);
         var actualScope = TryGetAttribute(otlpLogRecord, scopeKey);
         Assert.NotNull(actualScope);
@@ -1039,7 +1087,8 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndMultipleScopesAreAdded_C
 
         // Assert.
         var logRecord = logRecords.Single();
-        var otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new());
+        var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord);
         var allScopeValues = otlpLogRecord.Attributes
             .Where(_ => _.Key == scopeKey1 || _.Key == scopeKey2)
             .Select(_ => _.Value.StringValue);
@@ -1080,7 +1129,8 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndMultipleScopeLevelsAreAd
 
         // Assert.
         var logRecord = logRecords.Single();
-        var otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new());
+        var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord);
         var allScopeValues = otlpLogRecord.Attributes
             .Where(_ => _.Key == scopeKey1 || _.Key == scopeKey2)
             .Select(_ => _.Value.StringValue);
@@ -1126,7 +1176,8 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeIsUsedInLogMethod_C
 
         // Assert.
         var logRecord = logRecords.Single();
-        var otlpLogRecord = logRecord.ToOtlpLog(DefaultSdkLimitOptions);
+        var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new());
+        var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord);
         var allScopeValues = otlpLogRecord.Attributes
             .Where(_ => _.Key == scopeKey1 || _.Key == scopeKey2)
             .Select(_ => _.Value.StringValue);