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 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 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; + } + } + + /// + /// Gets or sets a value indicating whether log exception attributes should be exported. + /// + 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 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 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(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(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; /// internal sealed class OtlpLogExporter : BaseExporter { - private readonly SdkLimitOptions sdkLimitOptions; private readonly IExportClient 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 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(); 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 { [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);