Skip to content

Commit b03e031

Browse files
authored
[sdk-1.5.0-hotfix] Removed support for parsing custom log states using reflection (#4614)
1 parent ae03840 commit b03e031

File tree

7 files changed

+58
-43
lines changed

7 files changed

+58
-43
lines changed

src/OpenTelemetry/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@
88
`IReadOnlyList` or `IEnumerable` of `KeyValuePair<string, object>`s.
99
([#4609](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4609))
1010

11+
* **Breaking Change** Removed the support for parsing `TState` types passed to
12+
the `ILogger.Log<TState>` API when `ParseStateValues` is true and `TState`
13+
does not implement either `IReadOnlyList<KeyValuePair<string, object>>` or
14+
`IEnumerable<KeyValuePair<string, object>>`. This feature was first introduced
15+
in the `1.5.0` stable release with
16+
[#4334](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4334) and
17+
has been removed because it makes the OpenTelemetry .NET SDK incompatible with
18+
native AOT.
19+
([#4614](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4614))
20+
1121
## 1.5.0
1222

1323
Released 2023-Jun-05

src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,17 @@ public void LoggerProviderException(string methodName, Exception ex)
158158
}
159159
}
160160

161+
[NonEvent]
162+
public void LoggerProcessStateSkipped<TState>()
163+
{
164+
if (this.IsEnabled(EventLevel.Warning, EventKeywords.All))
165+
{
166+
this.LoggerProcessStateSkipped(
167+
typeof(TState).FullName!,
168+
"because it does not implement a supported interface (either IReadOnlyList<KeyValuePair<string, object>> or IEnumerable<KeyValuePair<string, object>>)");
169+
}
170+
}
171+
161172
[Event(4, Message = "Unknown error in SpanProcessor event '{0}': '{1}'.", Level = EventLevel.Error)]
162173
public void SpanProcessorException(string evnt, string ex)
163174
{
@@ -325,6 +336,12 @@ public void LoggerProviderException(string methodName, string ex)
325336
this.WriteEvent(50, methodName, ex);
326337
}
327338

339+
[Event(51, Message = "Skipped processing log state of type '{0}' {1}.", Level = EventLevel.Warning)]
340+
public void LoggerProcessStateSkipped(string type, string reason)
341+
{
342+
this.WriteEvent(51, type, reason);
343+
}
344+
328345
#if DEBUG
329346
public class OpenTelemetryEventListener : EventListener
330347
{

src/OpenTelemetry/Logs/ILogger/OpenTelemetryLogger.cs

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
#nullable enable
1818

19-
using System.ComponentModel;
2019
using System.Diagnostics;
2120
using System.Diagnostics.CodeAnalysis;
2221
using System.Runtime.CompilerServices;
@@ -207,6 +206,7 @@ internal static void SetLogRecordSeverityFields(ref LogRecordData logRecordData,
207206
// Note: This is to preserve legacy behavior where State is
208207
// exposed if we didn't parse state.
209208
iLoggerData.State = state;
209+
210210
return null;
211211
}
212212
else
@@ -215,36 +215,9 @@ internal static void SetLogRecordSeverityFields(ref LogRecordData logRecordData,
215215
// have come from the pool and may have State from a prior log.
216216
iLoggerData.State = null;
217217

218-
try
219-
{
220-
PropertyDescriptorCollection itemProperties = TypeDescriptor.GetProperties(state!);
221-
222-
var attributeStorage = logRecord.AttributeStorage ??= new List<KeyValuePair<string, object?>>(itemProperties.Count);
223-
224-
foreach (PropertyDescriptor? itemProperty in itemProperties)
225-
{
226-
if (itemProperty == null)
227-
{
228-
continue;
229-
}
230-
231-
object? value = itemProperty.GetValue(state);
232-
if (value == null)
233-
{
234-
continue;
235-
}
236-
237-
attributeStorage.Add(new KeyValuePair<string, object?>(itemProperty.Name, value));
238-
}
218+
OpenTelemetrySdkEventSource.Log.LoggerProcessStateSkipped<TState>();
239219

240-
return attributeStorage;
241-
}
242-
catch (Exception parseException)
243-
{
244-
OpenTelemetrySdkEventSource.Log.LoggerParseStateException<TState>(parseException);
245-
246-
return Array.Empty<KeyValuePair<string, object?>>();
247-
}
220+
return Array.Empty<KeyValuePair<string, object?>>();
248221
}
249222
}
250223

src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggerOptions.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,21 @@ public class OpenTelemetryLoggerOptions
5858
/// <remarks>
5959
/// Notes:
6060
/// <list type="bullet">
61-
/// <item>Parsing is only executed when the state logged does NOT
62-
/// implement <see cref="IReadOnlyList{T}"/> or <see
61+
/// <item>As of OpenTelemetry v1.5 state parsing is handled automatically if
62+
/// the state logged implements <see cref="IReadOnlyList{T}"/> or <see
6363
/// cref="IEnumerable{T}"/> where <c>T</c> is <c>KeyValuePair&lt;string,
64-
/// object&gt;</c>.</item>
64+
/// object&gt;</c> than <see cref="LogRecord.Attributes"/> will be set
65+
/// regardless of the value of <see cref="ParseStateValues"/>.</item>
6566
/// <item>When <see cref="ParseStateValues"/> is set to <see
6667
/// langword="true"/> <see cref="LogRecord.State"/> will always be <see
67-
/// langword="null"/>.</item>
68+
/// langword="null"/>. When <see cref="ParseStateValues"/> is set to <see
69+
/// langword="false"/> <see cref="LogRecord.State"/> will always be set to
70+
/// the logged state to support legacy exporters which access <see
71+
/// cref="LogRecord.State"/> directly. Exporters should NOT access <see
72+
/// cref="LogRecord.State"/> directly because is NOT safe and may lead to
73+
/// exceptions or incorrect data especially when using batching. Exporters
74+
/// should use <see cref="LogRecord.Attributes"/> to safely access any data
75+
/// attached to log messages.</item>
6876
/// </list>
6977
/// </remarks>
7078
public bool ParseStateValues { get; set; }

test/OpenTelemetry.AotCompatibility.Tests/AotCompatibilityTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public void EnsureAotCompatibility()
8585
Assert.True(process.ExitCode == 0, "Publishing the AotCompatibility app failed. See test output for more details.");
8686

8787
var warnings = expectedOutput.ToString().Split('\n', '\r').Where(line => line.Contains("warning IL"));
88-
Assert.Equal(43, warnings.Count());
88+
Assert.Equal(42, warnings.Count());
8989
}
9090
}
9191
}

test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,11 @@ public void AddOtlpLogExporterParseStateValueCanBeTurnedOff(bool parseState)
100100
{
101101
Assert.Null(logRecord.State);
102102
Assert.NotNull(logRecord.Attributes);
103-
Assert.Contains(logRecord.Attributes, kvp => kvp.Key == "propertyA" && (string)kvp.Value == "valueA");
103+
104+
// Note: We currently do not support parsing custom states which do
105+
// not implement the standard interfaces. We return empty attributes
106+
// for these.
107+
Assert.Empty(logRecord.Attributes);
104108
}
105109
else
106110
{
@@ -140,7 +144,11 @@ public void AddOtlpLogExporterParseStateValueCanBeTurnedOffHosting(bool parseSta
140144
{
141145
Assert.Null(logRecord.State);
142146
Assert.NotNull(logRecord.Attributes);
143-
Assert.Contains(logRecord.Attributes, kvp => kvp.Key == "propertyA" && (string)kvp.Value == "valueA");
147+
148+
// Note: We currently do not support parsing custom states which do
149+
// not implement the standard interfaces. We return empty attributes
150+
// for these.
151+
Assert.Empty(logRecord.Attributes);
144152
}
145153
else
146154
{

test/OpenTelemetry.Tests/Logs/LogRecordTest.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -826,7 +826,7 @@ public void ParseStateValuesUsingIEnumerableTest()
826826
}
827827

828828
[Fact]
829-
public void ParseStateValuesUsingCustomTest()
829+
public void ParseStateValuesUsingNonconformingCustomTypeTest()
830830
{
831831
using var loggerFactory = InitializeLoggerFactory(out List<LogRecord> exportedItems, configure: options => options.ParseStateValues = true);
832832
var logger = loggerFactory.CreateLogger<LogRecordTest>();
@@ -848,12 +848,11 @@ public void ParseStateValuesUsingCustomTest()
848848

849849
Assert.Null(logRecord.State);
850850
Assert.NotNull(logRecord.StateValues);
851-
Assert.Equal(1, logRecord.StateValues.Count);
852-
853-
KeyValuePair<string, object> actualState = logRecord.StateValues[0];
854851

855-
Assert.Equal("Property", actualState.Key);
856-
Assert.Equal("Value", actualState.Value);
852+
// Note: We currently do not support parsing custom states which do
853+
// not implement the standard interfaces. We return empty attributes
854+
// for these.
855+
Assert.Empty(logRecord.StateValues);
857856
}
858857

859858
[Fact]

0 commit comments

Comments
 (0)