diff --git a/src/OpenTelemetry.Api/InstrumentationScope.cs b/src/OpenTelemetry.Api/InstrumentationScope.cs
new file mode 100644
index 00000000000..9b6540d032e
--- /dev/null
+++ b/src/OpenTelemetry.Api/InstrumentationScope.cs
@@ -0,0 +1,65 @@
+//
+// 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.
+//
+
+#nullable enable
+
+namespace OpenTelemetry;
+
+///
+/// Contains details about the library emitting telemetry.
+///
+internal sealed class InstrumentationScope
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public InstrumentationScope()
+ : this(name: null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Optional name identifying the instrumentation library.
+ public InstrumentationScope(string? name)
+ {
+ this.Name = string.IsNullOrWhiteSpace(name)
+ ? string.Empty
+ : name!;
+ }
+
+ ///
+ /// Gets the name identifying the instrumentation library.
+ ///
+ public string Name { get; }
+
+ ///
+ /// Gets the version of the instrumentation library.
+ ///
+ public string? Version { get; init; }
+
+ ///
+ /// Gets the schema url of the instrumentation library.
+ ///
+ public string? SchemaUrl { get; init; }
+
+ ///
+ /// Gets the attributes which should be associated with log records created
+ /// by the instrumentation library.
+ ///
+ public IReadOnlyDictionary? Attributes { get; init; }
+}
diff --git a/src/OpenTelemetry/Internal/Shims/IsExternalInit.cs b/src/OpenTelemetry.Api/Internal/Shims/IsExternalInit.cs
similarity index 100%
rename from src/OpenTelemetry/Internal/Shims/IsExternalInit.cs
rename to src/OpenTelemetry.Api/Internal/Shims/IsExternalInit.cs
diff --git a/src/OpenTelemetry.Api/Logs/IDeferredLoggerProviderBuilder.cs b/src/OpenTelemetry.Api/Logs/IDeferredLoggerProviderBuilder.cs
new file mode 100644
index 00000000000..698272fda89
--- /dev/null
+++ b/src/OpenTelemetry.Api/Logs/IDeferredLoggerProviderBuilder.cs
@@ -0,0 +1,36 @@
+//
+// 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.
+//
+
+#nullable enable
+
+namespace OpenTelemetry.Logs;
+
+///
+/// Describes a logger provider builder that supports deferred
+/// initialization using an to perform
+/// dependency injection.
+///
+internal interface IDeferredLoggerProviderBuilder
+{
+ ///
+ /// Register a callback action to configure the once the application is available.
+ ///
+ /// Configuration callback.
+ /// The supplied for chaining.
+ LoggerProviderBuilder Configure(Action configure);
+}
diff --git a/src/OpenTelemetry.Api/Logs/LogRecordAttributeList.cs b/src/OpenTelemetry.Api/Logs/LogRecordAttributeList.cs
new file mode 100644
index 00000000000..ffe70191c5e
--- /dev/null
+++ b/src/OpenTelemetry.Api/Logs/LogRecordAttributeList.cs
@@ -0,0 +1,333 @@
+//
+// 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.
+//
+
+#nullable enable
+
+using System.Collections;
+using System.ComponentModel;
+using System.Diagnostics;
+using OpenTelemetry.Internal;
+using OpenTelemetry.Trace;
+
+namespace OpenTelemetry.Logs;
+
+///
+/// Stores attributes to be added to a log message.
+///
+internal struct LogRecordAttributeList : IReadOnlyList>
+{
+ internal const int OverflowMaxCount = 8;
+ internal const int OverflowAdditionalCapacity = 16;
+ internal List>? OverflowAttributes;
+ private KeyValuePair attribute1;
+ private KeyValuePair attribute2;
+ private KeyValuePair attribute3;
+ private KeyValuePair attribute4;
+ private KeyValuePair attribute5;
+ private KeyValuePair attribute6;
+ private KeyValuePair attribute7;
+ private KeyValuePair attribute8;
+ private int count;
+
+ ///
+ public readonly int Count => this.count;
+
+ ///
+ public KeyValuePair this[int index]
+ {
+ readonly get
+ {
+ if (this.OverflowAttributes is not null)
+ {
+ Debug.Assert(index < this.OverflowAttributes.Count, "Invalid index accessed.");
+ return this.OverflowAttributes[index];
+ }
+
+ if ((uint)index >= (uint)this.count)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+
+ return index switch
+ {
+ 0 => this.attribute1,
+ 1 => this.attribute2,
+ 2 => this.attribute3,
+ 3 => this.attribute4,
+ 4 => this.attribute5,
+ 5 => this.attribute6,
+ 6 => this.attribute7,
+ 7 => this.attribute8,
+ _ => default, // we shouldn't come here anyway.
+ };
+ }
+
+ set
+ {
+ if (this.OverflowAttributes is not null)
+ {
+ Debug.Assert(index < this.OverflowAttributes.Count, "Invalid index accessed.");
+ this.OverflowAttributes[index] = value;
+ return;
+ }
+
+ if ((uint)index >= (uint)this.count)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+
+ switch (index)
+ {
+ case 0: this.attribute1 = value; break;
+ case 1: this.attribute2 = value; break;
+ case 2: this.attribute3 = value; break;
+ case 3: this.attribute4 = value; break;
+ case 4: this.attribute5 = value; break;
+ case 5: this.attribute6 = value; break;
+ case 6: this.attribute7 = value; break;
+ case 7: this.attribute8 = value; break;
+ default:
+ Debug.Assert(false, "Unreachable code executed.");
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Add an attribute.
+ ///
+ /// Attribute name.
+ /// Attribute value.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public object? this[string key]
+ {
+ // Note: This only exists to enable collection initializer syntax
+ // like { ["key"] = value }.
+ set => this.Add(new KeyValuePair(key, value));
+ }
+
+ ///
+ /// Create a collection from an enumerable.
+ ///
+ /// Source attributes.
+ /// .
+ public static LogRecordAttributeList CreateFromEnumerable(IEnumerable> attributes)
+ {
+ Guard.ThrowIfNull(attributes);
+
+ LogRecordAttributeList logRecordAttributes = default;
+ logRecordAttributes.OverflowAttributes = new(attributes);
+ logRecordAttributes.count = logRecordAttributes.OverflowAttributes.Count;
+ return logRecordAttributes;
+ }
+
+ ///
+ /// Add an attribute.
+ ///
+ /// Attribute name.
+ /// Attribute value.
+ public void Add(string key, object? value)
+ => this.Add(new KeyValuePair(key, value));
+
+ ///
+ /// Add an attribute.
+ ///
+ /// Attribute.
+ public void Add(KeyValuePair attribute)
+ {
+ var count = this.count++;
+
+ if (count <= OverflowMaxCount)
+ {
+ switch (count)
+ {
+ case 0: this.attribute1 = attribute; return;
+ case 1: this.attribute2 = attribute; return;
+ case 2: this.attribute3 = attribute; return;
+ case 3: this.attribute4 = attribute; return;
+ case 4: this.attribute5 = attribute; return;
+ case 5: this.attribute6 = attribute; return;
+ case 6: this.attribute7 = attribute; return;
+ case 7: this.attribute8 = attribute; return;
+ case 8:
+ this.MoveAttributesToTheOverflowList();
+ break;
+ }
+ }
+
+ Debug.Assert(this.OverflowAttributes is not null, "Overflow attributes creation failure.");
+ this.OverflowAttributes!.Add(attribute);
+ }
+
+ ///
+ /// Removes all elements from the .
+ ///
+ public void Clear()
+ {
+ this.count = 0;
+ this.OverflowAttributes?.Clear();
+ }
+
+ ///
+ /// Adds attributes representing an to the list.
+ ///
+ /// .
+ public void RecordException(Exception exception)
+ {
+ Guard.ThrowIfNull(exception);
+
+ this.Add(SemanticConventions.AttributeExceptionType, exception.GetType().Name);
+ this.Add(SemanticConventions.AttributeExceptionMessage, exception.Message);
+ this.Add(SemanticConventions.AttributeExceptionStacktrace, exception.ToInvariantString());
+ }
+
+ ///
+ /// Returns an enumerator that iterates through the .
+ ///
+ /// .
+ public readonly Enumerator GetEnumerator()
+ => new(in this);
+
+ ///
+ readonly IEnumerator> IEnumerable>.GetEnumerator() => this.GetEnumerator();
+
+ ///
+ readonly IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
+
+ internal readonly List>? Export(ref List>? attributeStorage)
+ {
+ int count = this.count;
+ if (count <= 0)
+ {
+ return null;
+ }
+
+ var overflowAttributes = this.OverflowAttributes;
+ if (overflowAttributes != null)
+ {
+ // An allocation has already occurred, just use the list.
+ return overflowAttributes;
+ }
+
+ Debug.Assert(count <= 8, "Invalid size detected.");
+
+ attributeStorage ??= new List>(OverflowAdditionalCapacity);
+
+ // TODO: Perf test this, adjust as needed.
+ if (count > 0)
+ {
+ attributeStorage.Add(this.attribute1);
+ if (count == 1)
+ {
+ return attributeStorage;
+ }
+
+ attributeStorage.Add(this.attribute2);
+ if (count == 2)
+ {
+ return attributeStorage;
+ }
+
+ attributeStorage.Add(this.attribute3);
+ if (count == 3)
+ {
+ return attributeStorage;
+ }
+
+ attributeStorage.Add(this.attribute4);
+ if (count == 4)
+ {
+ return attributeStorage;
+ }
+
+ attributeStorage.Add(this.attribute5);
+ if (count == 5)
+ {
+ return attributeStorage;
+ }
+
+ attributeStorage.Add(this.attribute6);
+ if (count == 6)
+ {
+ return attributeStorage;
+ }
+
+ attributeStorage.Add(this.attribute7);
+ if (count == 7)
+ {
+ return attributeStorage;
+ }
+
+ attributeStorage.Add(this.attribute8);
+ }
+
+ return attributeStorage;
+ }
+
+ private void MoveAttributesToTheOverflowList()
+ {
+ Debug.Assert(this.count - 1 == OverflowMaxCount, "count did not match OverflowMaxCount");
+
+ var attributes = this.OverflowAttributes ??= new(OverflowAdditionalCapacity);
+
+ attributes.Add(this.attribute1);
+ attributes.Add(this.attribute2);
+ attributes.Add(this.attribute3);
+ attributes.Add(this.attribute4);
+ attributes.Add(this.attribute5);
+ attributes.Add(this.attribute6);
+ attributes.Add(this.attribute7);
+ attributes.Add(this.attribute8);
+ }
+
+ ///
+ /// Enumerates the elements of a .
+ ///
+ public struct Enumerator : IEnumerator>, IEnumerator
+ {
+ private LogRecordAttributeList attributes;
+ private int index;
+
+ internal Enumerator(in LogRecordAttributeList attributes)
+ {
+ this.index = -1;
+ this.attributes = attributes;
+ }
+
+ ///
+ public readonly KeyValuePair Current
+ => this.attributes[this.index];
+
+ ///
+ readonly object IEnumerator.Current => this.Current;
+
+ ///
+ public bool MoveNext()
+ {
+ this.index++;
+ return this.index < this.attributes.Count;
+ }
+
+ ///
+ public readonly void Dispose()
+ {
+ }
+
+ ///
+ readonly void IEnumerator.Reset()
+ => throw new NotSupportedException();
+ }
+}
diff --git a/src/OpenTelemetry.Api/Logs/LogRecordData.cs b/src/OpenTelemetry.Api/Logs/LogRecordData.cs
new file mode 100644
index 00000000000..fec2c11dbe8
--- /dev/null
+++ b/src/OpenTelemetry.Api/Logs/LogRecordData.cs
@@ -0,0 +1,140 @@
+//
+// 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.
+//
+
+#nullable enable
+
+using System.Diagnostics;
+
+namespace OpenTelemetry.Logs;
+
+///
+/// Stores details about a log message.
+///
+internal struct LogRecordData
+{
+ internal DateTime TimestampBacking = DateTime.UtcNow;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ ///
+ /// Notes:
+ ///
+ /// - The property is initialized to automatically.
+ /// - The , , and properties will be set using the instance.
+ ///
+ ///
+ public LogRecordData()
+ : this(Activity.Current?.Context ?? default)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ ///
+ /// Note: The property is initialized to automatically.
+ ///
+ /// Optional used to populate
+ /// trace context properties (, ,
+ /// and ).
+ public LogRecordData(Activity? activity)
+ : this(activity?.Context ?? default)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ ///
+ /// Note: The property is initialized to automatically.
+ ///
+ /// used to
+ /// populate trace context properties (, , and ).
+ public LogRecordData(in ActivityContext activityContext)
+ {
+ this.TraceId = activityContext.TraceId;
+ this.SpanId = activityContext.SpanId;
+ this.TraceFlags = activityContext.TraceFlags;
+ }
+
+ ///
+ /// Gets or sets the log timestamp.
+ ///
+ ///
+ /// Note: If is set to a value with it will be automatically converted to
+ /// UTC using .
+ ///
+ public DateTime Timestamp
+ {
+ readonly get => this.TimestampBacking;
+ set { this.TimestampBacking = value.Kind == DateTimeKind.Local ? value.ToUniversalTime() : value; }
+ }
+
+ ///
+ /// Gets or sets the log .
+ ///
+ public ActivityTraceId TraceId { get; set; }
+
+ ///
+ /// Gets or sets the log .
+ ///
+ public ActivitySpanId SpanId { get; set; }
+
+ ///
+ /// Gets or sets the log .
+ ///
+ public ActivityTraceFlags TraceFlags { get; set; }
+
+ ///
+ /// Gets or sets the original string representation of the severity as it is
+ /// known at the source.
+ ///
+ public string? SeverityText { get; set; } = null;
+
+ ///
+ /// Gets or sets the log severity.
+ ///
+ public LogRecordSeverity? Severity { get; set; } = null;
+
+ ///
+ /// Gets or sets the log body.
+ ///
+ public string? Body { get; set; } = null;
+
+ internal static void SetActivityContext(ref LogRecordData data, Activity? activity)
+ {
+ if (activity != null)
+ {
+ data.TraceId = activity.TraceId;
+ data.SpanId = activity.SpanId;
+ data.TraceFlags = activity.ActivityTraceFlags;
+ }
+ else
+ {
+ data.TraceId = default;
+ data.SpanId = default;
+ data.TraceFlags = ActivityTraceFlags.None;
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Api/Logs/LogRecordSeverity.cs b/src/OpenTelemetry.Api/Logs/LogRecordSeverity.cs
new file mode 100644
index 00000000000..3256b530952
--- /dev/null
+++ b/src/OpenTelemetry.Api/Logs/LogRecordSeverity.cs
@@ -0,0 +1,43 @@
+//
+// 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.
+//
+
+#nullable enable
+
+namespace OpenTelemetry.Logs;
+
+///
+/// Describes the severity level of a log record.
+///
+internal enum LogRecordSeverity
+{
+ /// Trace severity.
+ Trace,
+
+ /// Debug severity.
+ Debug,
+
+ /// Information severity.
+ Information,
+
+ /// Warning severity.
+ Warning,
+
+ /// Error severity.
+ Error,
+
+ /// Fatal severity.
+ Fatal,
+}
diff --git a/src/OpenTelemetry.Api/Logs/Logger.cs b/src/OpenTelemetry.Api/Logs/Logger.cs
new file mode 100644
index 00000000000..6addbadd20e
--- /dev/null
+++ b/src/OpenTelemetry.Api/Logs/Logger.cs
@@ -0,0 +1,54 @@
+//
+// 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.
+//
+
+#nullable enable
+
+using OpenTelemetry.Internal;
+
+namespace OpenTelemetry.Logs;
+
+///
+/// Logger is the class responsible for creating log records.
+///
+internal abstract class Logger
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// .
+ protected Logger(InstrumentationScope instrumentationScope)
+ {
+ Guard.ThrowIfNull(instrumentationScope);
+
+ this.InstrumentationScope = instrumentationScope;
+ }
+
+ ///
+ /// Gets the associated
+ /// with the logger.
+ ///
+ public InstrumentationScope InstrumentationScope { get; }
+
+ ///
+ /// Emit a log.
+ ///
+ /// .
+ /// .
+ public abstract void EmitLog(
+ in LogRecordData data,
+ in LogRecordAttributeList attributes = default);
+}
diff --git a/src/OpenTelemetry.Api/Logs/LoggerProvider.cs b/src/OpenTelemetry.Api/Logs/LoggerProvider.cs
new file mode 100644
index 00000000000..6e0f07658b6
--- /dev/null
+++ b/src/OpenTelemetry.Api/Logs/LoggerProvider.cs
@@ -0,0 +1,57 @@
+//
+// 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.
+//
+
+#nullable enable
+
+namespace OpenTelemetry.Logs;
+
+///
+/// LoggerProvider is the entry point of the OpenTelemetry API. It provides access to .
+///
+internal class LoggerProvider : BaseProvider
+{
+ private static readonly NoopLogger NoopLogger = new();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected LoggerProvider()
+ {
+ }
+
+ ///
+ /// Gets a logger.
+ ///
+ /// instance.
+ public Logger GetLogger()
+ => this.GetLogger(new InstrumentationScope());
+
+ ///
+ /// Gets a logger with the given name.
+ ///
+ /// Optional name identifying the instrumentation library.
+ /// instance.
+ public Logger GetLogger(string name)
+ => this.GetLogger(new InstrumentationScope(name));
+
+ ///
+ /// Gets a logger with given instrumentation scope.
+ ///
+ /// .
+ /// .
+ public virtual Logger GetLogger(InstrumentationScope instrumentationScope)
+ => NoopLogger;
+}
diff --git a/src/OpenTelemetry.Api/Logs/LoggerProviderBuilder.cs b/src/OpenTelemetry.Api/Logs/LoggerProviderBuilder.cs
new file mode 100644
index 00000000000..5e525e73190
--- /dev/null
+++ b/src/OpenTelemetry.Api/Logs/LoggerProviderBuilder.cs
@@ -0,0 +1,42 @@
+//
+// 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.
+//
+
+#nullable enable
+
+namespace OpenTelemetry.Logs;
+
+///
+/// LoggerProviderBuilder base class.
+///
+internal abstract class LoggerProviderBuilder
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected LoggerProviderBuilder()
+ {
+ }
+
+ ///
+ /// Adds instrumentation to the provider.
+ ///
+ /// Type of instrumentation class.
+ /// Function that builds instrumentation.
+ /// Returns for chaining.
+ public abstract LoggerProviderBuilder AddInstrumentation(
+ Func instrumentationFactory)
+ where TInstrumentation : class;
+}
diff --git a/src/OpenTelemetry.Api/Logs/NoopLogger.cs b/src/OpenTelemetry.Api/Logs/NoopLogger.cs
new file mode 100644
index 00000000000..62c74ae8e6e
--- /dev/null
+++ b/src/OpenTelemetry.Api/Logs/NoopLogger.cs
@@ -0,0 +1,33 @@
+//
+// 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.
+//
+
+#nullable enable
+
+namespace OpenTelemetry.Logs;
+
+internal sealed class NoopLogger : Logger
+{
+ public NoopLogger()
+ : base(instrumentationScope: new())
+ {
+ }
+
+ public override void EmitLog(
+ in LogRecordData data,
+ in LogRecordAttributeList attributes = default)
+ {
+ }
+}
diff --git a/src/OpenTelemetry/Logs/LogRecordAttributeList.cs b/src/OpenTelemetry/Logs/LogRecordAttributeList.cs
deleted file mode 100644
index 3b19a84b5f0..00000000000
--- a/src/OpenTelemetry/Logs/LogRecordAttributeList.cs
+++ /dev/null
@@ -1,324 +0,0 @@
-//
-// 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.
-//
-
-#nullable enable
-
-using System.Collections;
-using System.ComponentModel;
-using System.Diagnostics;
-using OpenTelemetry.Internal;
-
-namespace OpenTelemetry.Logs
-{
- ///
- /// Stores attributes to be added to a log message.
- ///
- internal struct LogRecordAttributeList : IReadOnlyList>
- {
- internal const int OverflowAdditionalCapacity = 8;
- internal List>? OverflowAttributes;
- private KeyValuePair attribute1;
- private KeyValuePair attribute2;
- private KeyValuePair attribute3;
- private KeyValuePair attribute4;
- private KeyValuePair attribute5;
- private KeyValuePair attribute6;
- private KeyValuePair attribute7;
- private KeyValuePair attribute8;
- private int count;
-
- ///
- public readonly int Count => this.count;
-
- ///
- public KeyValuePair this[int index]
- {
- readonly get
- {
- if (this.OverflowAttributes is not null)
- {
- Debug.Assert(index < this.OverflowAttributes.Count, "Invalid index accessed.");
- return this.OverflowAttributes[index];
- }
-
- if ((uint)index >= (uint)this.count)
- {
- throw new ArgumentOutOfRangeException(nameof(index));
- }
-
- return index switch
- {
- 0 => this.attribute1,
- 1 => this.attribute2,
- 2 => this.attribute3,
- 3 => this.attribute4,
- 4 => this.attribute5,
- 5 => this.attribute6,
- 6 => this.attribute7,
- 7 => this.attribute8,
- _ => default, // we shouldn't come here anyway.
- };
- }
-
- set
- {
- if (this.OverflowAttributes is not null)
- {
- Debug.Assert(index < this.OverflowAttributes.Count, "Invalid index accessed.");
- this.OverflowAttributes[index] = value;
- return;
- }
-
- if ((uint)index >= (uint)this.count)
- {
- throw new ArgumentOutOfRangeException(nameof(index));
- }
-
- switch (index)
- {
- case 0: this.attribute1 = value; break;
- case 1: this.attribute2 = value; break;
- case 2: this.attribute3 = value; break;
- case 3: this.attribute4 = value; break;
- case 4: this.attribute5 = value; break;
- case 5: this.attribute6 = value; break;
- case 6: this.attribute7 = value; break;
- case 7: this.attribute8 = value; break;
- default:
- Debug.Assert(false, "Unreachable code executed.");
- break;
- }
- }
- }
-
- ///
- /// Add an attribute.
- ///
- /// Attribute name.
- /// Attribute value.
- [EditorBrowsable(EditorBrowsableState.Never)]
- public object? this[string key]
- {
- // Note: This only exists to enable collection initializer syntax
- // like { ["key"] = value }.
- set => this.Add(new KeyValuePair(key, value));
- }
-
- ///
- /// Create a collection from an enumerable.
- ///
- /// Source attributes.
- /// .
- public static LogRecordAttributeList CreateFromEnumerable(IEnumerable> attributes)
- {
- Guard.ThrowIfNull(attributes);
-
- LogRecordAttributeList logRecordAttributes = default;
- logRecordAttributes.OverflowAttributes = new(attributes);
- logRecordAttributes.count = logRecordAttributes.OverflowAttributes.Count;
- return logRecordAttributes;
- }
-
- ///
- /// Add an attribute.
- ///
- /// Attribute name.
- /// Attribute value.
- public void Add(string key, object? value)
- => this.Add(new KeyValuePair(key, value));
-
- ///
- /// Add an attribute.
- ///
- /// Attribute.
- public void Add(KeyValuePair attribute)
- {
- if (this.OverflowAttributes is not null)
- {
- this.OverflowAttributes.Add(attribute);
- this.count++;
- return;
- }
-
- Debug.Assert(this.count <= 8, "Item added beyond struct capacity.");
-
- switch (this.count)
- {
- case 0: this.attribute1 = attribute; break;
- case 1: this.attribute2 = attribute; break;
- case 2: this.attribute3 = attribute; break;
- case 3: this.attribute4 = attribute; break;
- case 4: this.attribute5 = attribute; break;
- case 5: this.attribute6 = attribute; break;
- case 6: this.attribute7 = attribute; break;
- case 7: this.attribute8 = attribute; break;
- case 8:
- Debug.Assert(this.OverflowAttributes is null, "Overflow attributes already created.");
- this.MoveAttributesToTheOverflowList();
- Debug.Assert(this.OverflowAttributes is not null, "Overflow attributes creation failure.");
- this.OverflowAttributes!.Add(attribute);
- break;
- default:
- // We shouldn't come here.
- Debug.Assert(this.OverflowAttributes is null, "Unreachable code executed.");
- return;
- }
-
- this.count++;
- }
-
- ///
- /// Returns an enumerator that iterates through the .
- ///
- /// .
- public readonly Enumerator GetEnumerator()
- => new(in this);
-
- ///
- readonly IEnumerator> IEnumerable>.GetEnumerator() => this.GetEnumerator();
-
- ///
- readonly IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
-
- internal readonly void ApplyToLogRecord(LogRecord logRecord)
- {
- int count = this.count;
- if (count <= 0)
- {
- logRecord.StateValues = null;
- return;
- }
-
- var overflowAttributes = this.OverflowAttributes;
- if (overflowAttributes != null)
- {
- // An allocation has already occurred, just use the buffer.
- logRecord.StateValues = overflowAttributes;
- return;
- }
-
- Debug.Assert(count <= 8, "Invalid size detected.");
-
- var attributeStorage = logRecord.AttributeStorage ??= new List>(OverflowAdditionalCapacity);
-
- try
- {
- // TODO: Perf test this, adjust as needed.
-
- attributeStorage.Add(this.attribute1);
- if (count == 1)
- {
- return;
- }
-
- attributeStorage.Add(this.attribute2);
- if (count == 2)
- {
- return;
- }
-
- attributeStorage.Add(this.attribute3);
- if (count == 3)
- {
- return;
- }
-
- attributeStorage.Add(this.attribute4);
- if (count == 4)
- {
- return;
- }
-
- attributeStorage.Add(this.attribute5);
- if (count == 5)
- {
- return;
- }
-
- attributeStorage.Add(this.attribute6);
- if (count == 6)
- {
- return;
- }
-
- attributeStorage.Add(this.attribute7);
- if (count == 7)
- {
- return;
- }
-
- attributeStorage.Add(this.attribute8);
- }
- finally
- {
- logRecord.StateValues = attributeStorage;
- }
- }
-
- private void MoveAttributesToTheOverflowList()
- {
- this.OverflowAttributes = new(16)
- {
- { this.attribute1 },
- { this.attribute2 },
- { this.attribute3 },
- { this.attribute4 },
- { this.attribute5 },
- { this.attribute6 },
- { this.attribute7 },
- { this.attribute8 },
- };
- }
-
- ///
- /// Enumerates the elements of a .
- ///
- public struct Enumerator : IEnumerator>, IEnumerator
- {
- private LogRecordAttributeList attributes;
- private int index;
-
- internal Enumerator(in LogRecordAttributeList attributes)
- {
- this.index = -1;
- this.attributes = attributes;
- }
-
- ///
- public readonly KeyValuePair Current
- => this.attributes[this.index];
-
- ///
- readonly object IEnumerator.Current => this.Current;
-
- ///
- public bool MoveNext()
- {
- this.index++;
- return this.index < this.attributes.Count;
- }
-
- ///
- public readonly void Dispose()
- {
- }
-
- ///
- readonly void IEnumerator.Reset()
- => throw new NotSupportedException();
- }
- }
-}
diff --git a/src/OpenTelemetry/Logs/LogRecordData.cs b/src/OpenTelemetry/Logs/LogRecordData.cs
deleted file mode 100644
index cd72425efe3..00000000000
--- a/src/OpenTelemetry/Logs/LogRecordData.cs
+++ /dev/null
@@ -1,125 +0,0 @@
-//
-// 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.
-//
-
-#nullable enable
-
-using System.Diagnostics;
-
-namespace OpenTelemetry.Logs
-{
- ///
- /// Stores details about a log message.
- ///
- internal struct LogRecordData
- {
- internal DateTime TimestampBacking = DateTime.UtcNow;
-
- ///
- /// Initializes a new instance of the struct.
- ///
- public LogRecordData()
- : this(activity: null)
- {
- }
-
- ///
- /// Initializes a new instance of the struct.
- ///
- ///
- /// Note: The property is initialized to automatically.
- ///
- /// Optional used to populate context fields.
- public LogRecordData(Activity? activity)
- {
- if (activity != null)
- {
- this.TraceId = activity.TraceId;
- this.SpanId = activity.SpanId;
- this.TraceFlags = activity.ActivityTraceFlags;
- }
- else
- {
- this.TraceId = default;
- this.SpanId = default;
- this.TraceFlags = ActivityTraceFlags.None;
- }
- }
-
- ///
- /// Gets or sets the log timestamp.
- ///
- ///
- /// Note: If is set to a value with it will be automatically converted to
- /// UTC using .
- ///
- public DateTime Timestamp
- {
- readonly get => this.TimestampBacking;
- set { this.TimestampBacking = value.Kind == DateTimeKind.Local ? value.ToUniversalTime() : value; }
- }
-
- ///
- /// Gets or sets the log .
- ///
- public ActivityTraceId TraceId { get; set; }
-
- ///
- /// Gets or sets the log .
- ///
- public ActivitySpanId SpanId { get; set; }
-
- ///
- /// Gets or sets the log .
- ///
- public ActivityTraceFlags TraceFlags { get; set; }
-
- /* TODO: Add these when log api/sdk spec is stable.
- ///
- /// Gets or sets the original string representation of the severity as it is
- /// known at the source.
- ///
- public string? SeverityText { get; set; } = null;
-
- ///
- /// Gets or sets the log severity.
- ///
- public LogRecordSeverity? Severity { get; set; } = null;
- */
-
- ///
- /// Gets or sets the log body.
- ///
- public string? Body { get; set; } = null;
-
- internal static void SetActivityContext(ref LogRecordData data, Activity? activity = null)
- {
- if (activity != null)
- {
- data.TraceId = activity.TraceId;
- data.SpanId = activity.SpanId;
- data.TraceFlags = activity.ActivityTraceFlags;
- }
- else
- {
- data.TraceId = default;
- data.SpanId = default;
- data.TraceFlags = ActivityTraceFlags.None;
- }
- }
- }
-}
diff --git a/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs b/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs
index 18d052584f2..2b92c974a33 100644
--- a/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs
+++ b/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs
@@ -125,9 +125,14 @@ public bool IsEnabled(LogLevel logLevel)
/* TODO: Enable this if/when LogRecordAttributeList becomes public.
if (typeof(TState) == typeof(LogRecordAttributeList))
{
- // Note: This cast looks strange, but it is meant for the JIT to optimize/remove.
- ((LogRecordAttributeList)(object)state!).ApplyToLogRecord(logRecord);
- return logRecord.AttributeStorage!;
+ // Note: This block is written to be elided by the JIT when
+ // TState is not LogRecordAttributeList or optimized when it is.
+ // For users that pass LogRecordAttributeList as TState to
+ // ILogger.Log this will avoid boxing the struct.
+
+ var logRecordAttributes = (LogRecordAttributeList)(object)state!;
+
+ return logRecordAttributes.Export(ref logRecord.AttributeStorage);
}
else */
if (state is IReadOnlyList> stateList)
diff --git a/test/OpenTelemetry.Api.Tests/Logs/LogRecordAttributeListTests.cs b/test/OpenTelemetry.Api.Tests/Logs/LogRecordAttributeListTests.cs
new file mode 100644
index 00000000000..21d856c9799
--- /dev/null
+++ b/test/OpenTelemetry.Api.Tests/Logs/LogRecordAttributeListTests.cs
@@ -0,0 +1,145 @@
+//
+// 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.
+//
+
+using Xunit;
+
+namespace OpenTelemetry.Logs.Tests;
+
+public sealed class LogRecordAttributeListTests
+{
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(8)]
+ [InlineData(9)]
+ [InlineData(64)]
+ public void ReadWriteTest(int numberOfItems)
+ {
+ LogRecordAttributeList attributes = default;
+
+ for (int i = 0; i < numberOfItems; i++)
+ {
+ attributes.Add($"key{i}", i);
+ }
+
+ Assert.Equal(numberOfItems, attributes.Count);
+
+ for (int i = 0; i < numberOfItems; i++)
+ {
+ var item = attributes[i];
+
+ Assert.Equal($"key{i}", item.Key);
+ Assert.Equal(i, (int)item.Value);
+ }
+
+ int index = 0;
+ foreach (KeyValuePair item in attributes)
+ {
+ Assert.Equal($"key{index}", item.Key);
+ Assert.Equal(index, (int)item.Value);
+ index++;
+ }
+
+ if (attributes.Count <= LogRecordAttributeList.OverflowMaxCount)
+ {
+ Assert.Null(attributes.OverflowAttributes);
+ }
+ else
+ {
+ Assert.NotNull(attributes.OverflowAttributes);
+ }
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(8)]
+ [InlineData(9)]
+ [InlineData(64)]
+ public void ClearTest(int numberOfItems)
+ {
+ LogRecordAttributeList attributes = default;
+
+ for (int c = 0; c <= 1; c++)
+ {
+ for (int i = 0; i < numberOfItems; i++)
+ {
+ attributes.Add($"key{i}", i);
+ }
+
+ Assert.Equal(numberOfItems, attributes.Count);
+
+ for (int i = 0; i < numberOfItems; i++)
+ {
+ var item = attributes[i];
+
+ Assert.Equal($"key{i}", item.Key);
+ Assert.Equal(i, (int)item.Value);
+ }
+
+ attributes.Clear();
+
+ Assert.Empty(attributes);
+ }
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(8)]
+ [InlineData(9)]
+ [InlineData(64)]
+ public void ExportTest(int numberOfItems)
+ {
+ LogRecordAttributeList attributes = default;
+
+ for (int i = 0; i < numberOfItems; i++)
+ {
+ attributes.Add($"key{i}", i);
+ }
+
+ List> storage = null;
+
+ var exportedAttributes = attributes.Export(ref storage);
+
+ if (numberOfItems == 0)
+ {
+ Assert.Null(exportedAttributes);
+ Assert.Null(storage);
+ return;
+ }
+
+ Assert.NotNull(exportedAttributes);
+
+ if (numberOfItems <= LogRecordAttributeList.OverflowMaxCount)
+ {
+ Assert.NotNull(storage);
+ Assert.True(ReferenceEquals(storage, exportedAttributes));
+ }
+ else
+ {
+ Assert.Null(storage);
+ }
+
+ int index = 0;
+ foreach (KeyValuePair item in exportedAttributes)
+ {
+ Assert.Equal($"key{index}", item.Key);
+ Assert.Equal(index, (int)item.Value);
+ index++;
+ }
+ }
+}
diff --git a/test/OpenTelemetry.Api.Tests/Logs/LogRecordDataTests.cs b/test/OpenTelemetry.Api.Tests/Logs/LogRecordDataTests.cs
new file mode 100644
index 00000000000..09d15e2b0f8
--- /dev/null
+++ b/test/OpenTelemetry.Api.Tests/Logs/LogRecordDataTests.cs
@@ -0,0 +1,139 @@
+//
+// 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.
+//
+
+using System.Diagnostics;
+using Xunit;
+
+namespace OpenTelemetry.Logs.Tests;
+
+public sealed class LogRecordDataTests
+{
+ [Fact]
+ public void ParameterlessConstructorWithActiveActivityTest()
+ {
+ using var activity = new Activity("Test");
+ activity.ActivityTraceFlags = ActivityTraceFlags.Recorded;
+ activity.Start();
+
+ var record = new LogRecordData();
+
+ Assert.Equal(activity.TraceId, record.TraceId);
+ Assert.Equal(activity.SpanId, record.SpanId);
+ Assert.Equal(activity.ActivityTraceFlags, record.TraceFlags);
+
+ record = default;
+
+ Assert.Equal(default, record.TraceId);
+ Assert.Equal(default, record.SpanId);
+ Assert.Equal(default, record.TraceFlags);
+ }
+
+ [Fact]
+ public void ParameterlessConstructorWithoutActiveActivityTest()
+ {
+ var record = new LogRecordData();
+
+ Assert.Equal(default, record.TraceId);
+ Assert.Equal(default, record.SpanId);
+ Assert.Equal(default, record.TraceFlags);
+ }
+
+ [Fact]
+ public void ActivityConstructorTest()
+ {
+ using var activity = new Activity("Test");
+ activity.ActivityTraceFlags = ActivityTraceFlags.Recorded;
+ activity.Start();
+
+ Activity.Current = null;
+
+ var record = new LogRecordData(activity);
+
+ Assert.Equal(activity.TraceId, record.TraceId);
+ Assert.Equal(activity.SpanId, record.SpanId);
+ Assert.Equal(activity.ActivityTraceFlags, record.TraceFlags);
+
+ record = new LogRecordData(activity: null);
+
+ Assert.Equal(default, record.TraceId);
+ Assert.Equal(default, record.SpanId);
+ Assert.Equal(default, record.TraceFlags);
+ }
+
+ [Fact]
+ public void ActivityContextConstructorTest()
+ {
+ var context = new ActivityContext(
+ ActivityTraceId.CreateRandom(),
+ ActivitySpanId.CreateRandom(),
+ ActivityTraceFlags.Recorded,
+ traceState: null,
+ isRemote: true);
+
+ var record = new LogRecordData(in context);
+
+ Assert.Equal(context.TraceId, record.TraceId);
+ Assert.Equal(context.SpanId, record.SpanId);
+ Assert.Equal(context.TraceFlags, record.TraceFlags);
+
+ record = new LogRecordData(activityContext: default);
+
+ Assert.Equal(default, record.TraceId);
+ Assert.Equal(default, record.SpanId);
+ Assert.Equal(default, record.TraceFlags);
+ }
+
+ [Fact]
+ public void TimestampTest()
+ {
+ var nowUtc = DateTime.UtcNow;
+
+ var record = new LogRecordData();
+ Assert.True(record.Timestamp >= nowUtc);
+
+ record = default;
+ Assert.Equal(DateTime.MinValue, record.Timestamp);
+
+ var now = DateTime.Now;
+
+ record.Timestamp = now;
+
+ Assert.Equal(DateTimeKind.Utc, record.Timestamp.Kind);
+ Assert.Equal(now.ToUniversalTime(), record.Timestamp);
+ }
+
+ [Fact]
+ public void SetActivityContextTest()
+ {
+ LogRecordData record = default;
+
+ using var activity = new Activity("Test");
+ activity.ActivityTraceFlags = ActivityTraceFlags.Recorded;
+ activity.Start();
+
+ LogRecordData.SetActivityContext(ref record, activity);
+
+ Assert.Equal(activity.TraceId, record.TraceId);
+ Assert.Equal(activity.SpanId, record.SpanId);
+ Assert.Equal(activity.ActivityTraceFlags, record.TraceFlags);
+
+ LogRecordData.SetActivityContext(ref record, activity: null);
+
+ Assert.Equal(default, record.TraceId);
+ Assert.Equal(default, record.SpanId);
+ Assert.Equal(default, record.TraceFlags);
+ }
+}
diff --git a/test/OpenTelemetry.Tests/Logs/LogRecordAttributeListTests.cs b/test/OpenTelemetry.Tests/Logs/LogRecordAttributeListTests.cs
deleted file mode 100644
index 255076614e2..00000000000
--- a/test/OpenTelemetry.Tests/Logs/LogRecordAttributeListTests.cs
+++ /dev/null
@@ -1,102 +0,0 @@
-//
-// 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.
-//
-
-using Xunit;
-
-namespace OpenTelemetry.Logs.Tests
-{
- public sealed class LogRecordAttributeListTests
- {
- [Theory]
- [InlineData(0)]
- [InlineData(1)]
- [InlineData(8)]
- [InlineData(9)]
- [InlineData(64)]
- public void ReadWriteTest(int numberOfItems)
- {
- LogRecordAttributeList attributes = default;
-
- for (int i = 0; i < numberOfItems; i++)
- {
- attributes.Add($"key{i}", i);
- }
-
- Assert.Equal(numberOfItems, attributes.Count);
-
- for (int i = 0; i < numberOfItems; i++)
- {
- var item = attributes[i];
-
- Assert.Equal($"key{i}", item.Key);
- Assert.Equal(i, (int)item.Value);
- }
-
- int index = 0;
- foreach (KeyValuePair item in attributes)
- {
- Assert.Equal($"key{index}", item.Key);
- Assert.Equal(index, (int)item.Value);
- index++;
- }
-
- if (attributes.Count <= LogRecordAttributeList.OverflowAdditionalCapacity)
- {
- Assert.Null(attributes.OverflowAttributes);
- }
- else
- {
- Assert.NotNull(attributes.OverflowAttributes);
- }
- }
-
- [Theory]
- [InlineData(0)]
- [InlineData(1)]
- [InlineData(8)]
- [InlineData(9)]
- [InlineData(64)]
- public void ApplyToLogRecordTest(int numberOfItems)
- {
- LogRecordAttributeList attributes = default;
-
- for (int i = 0; i < numberOfItems; i++)
- {
- attributes.Add($"key{i}", i);
- }
-
- LogRecord logRecord = new();
-
- attributes.ApplyToLogRecord(logRecord);
-
- if (numberOfItems == 0)
- {
- Assert.Null(logRecord.StateValues);
- return;
- }
-
- Assert.NotNull(logRecord.StateValues);
-
- int index = 0;
- foreach (KeyValuePair item in logRecord.StateValues)
- {
- Assert.Equal($"key{index}", item.Key);
- Assert.Equal(index, (int)item.Value);
- index++;
- }
- }
- }
-}