diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md index 9c863ba36cf..7482ea96f6e 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md @@ -2,9 +2,12 @@ ## Unreleased -* LogExporter to correcly map Severity to OTLP. +* LogExporter to correctly map Severity to OTLP. ([#3177](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3177)) +* LogExporter to special case {OriginalFormat} to populate + Body. ([#3182](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3182)) + ## 1.2.0-rc5 Released 2022-Apr-12 diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/LogRecordExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/LogRecordExtensions.cs index 323665790ce..1392aea00bc 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/LogRecordExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/LogRecordExtensions.cs @@ -76,17 +76,29 @@ internal static OtlpLogs.LogRecord ToOtlpLog(this LogRecord logRecord) // TODO: Add logRecord.CategoryName as an attribute + bool bodyPopulatedFromFormattedMessage = false; if (logRecord.FormattedMessage != null) { otlpLogRecord.Body = new OtlpCommon.AnyValue { StringValue = logRecord.FormattedMessage }; + bodyPopulatedFromFormattedMessage = true; } if (logRecord.StateValues != null) { foreach (var stateValue in logRecord.StateValues) { - var otlpAttribute = stateValue.ToOtlpAttribute(); - otlpLogRecord.Attributes.Add(otlpAttribute); + // Special casing {OriginalFormat} + // See https://github.com/open-telemetry/opentelemetry-dotnet/pull/3182 + // for explanation. + if (stateValue.Key.Equals("{OriginalFormat}") && !bodyPopulatedFromFormattedMessage) + { + otlpLogRecord.Body = new OtlpCommon.AnyValue { StringValue = stateValue.Value as string }; + } + else + { + var otlpAttribute = stateValue.ToOtlpAttribute(); + otlpLogRecord.Attributes.Add(otlpAttribute); + } } } diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs index 747fe037617..d9a6942fbea 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs @@ -225,8 +225,10 @@ public void CheckToOtlpLogRecordSeverityLevelAndText(LogLevel logLevel) } } - [Fact] - public void CheckToOtlpLogRecordFormattedMessage() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void CheckToOtlpLogRecordBodyIsPopulated(bool includeFormattedMessage) { var logRecords = new List(); using var loggerFactory = LoggerFactory.Create(builder => @@ -234,11 +236,14 @@ public void CheckToOtlpLogRecordFormattedMessage() builder.AddOpenTelemetry(options => { options.AddInMemoryExporter(logRecords); - options.IncludeFormattedMessage = true; + options.IncludeFormattedMessage = includeFormattedMessage; + options.ParseStateValues = true; }); }); var logger = loggerFactory.CreateLogger("OtlpLogExporterTests"); + + // Scenario 1 - Using ExtensionMethods on ILogger.Log logger.LogInformation("OpenTelemetry {Greeting} {Subject}!", "Hello", "World"); Assert.Single(logRecords); @@ -246,7 +251,56 @@ public void CheckToOtlpLogRecordFormattedMessage() var otlpLogRecord = logRecord.ToOtlpLog(); Assert.NotNull(otlpLogRecord); - Assert.Equal(logRecord.FormattedMessage, otlpLogRecord.Body.StringValue); + if (includeFormattedMessage) + { + Assert.Equal(logRecord.FormattedMessage, otlpLogRecord.Body.StringValue); + } + else + { + Assert.Equal("OpenTelemetry {Greeting} {Subject}!", otlpLogRecord.Body.StringValue); + } + + logRecords.Clear(); + + // Scenario 2 - Using the raw ILogger.Log Method + logger.Log(LogLevel.Information, default, "state", exception: null, (st, ex) => "Formatted Message"); + Assert.Single(logRecords); + + logRecord = logRecords[0]; + otlpLogRecord = logRecord.ToOtlpLog(); + + Assert.NotNull(otlpLogRecord); + if (includeFormattedMessage) + { + Assert.Equal(logRecord.FormattedMessage, otlpLogRecord.Body.StringValue); + } + else + { + Assert.Null(otlpLogRecord.Body); + } + + logRecords.Clear(); + + // Scenario 3 - Using the raw ILogger.Log Method, but with null + // formatter. + logger.Log(LogLevel.Information, default, "state", exception: null, formatter: null); + Assert.Single(logRecords); + + logRecord = logRecords[0]; + otlpLogRecord = logRecord.ToOtlpLog(); + + Assert.NotNull(otlpLogRecord); + + // There is no formatter, so no way to populate Body. + // Exporter won't even attempt to do ToString() on State. + if (includeFormattedMessage) + { + Assert.Null(otlpLogRecord.Body); + } + else + { + Assert.Null(otlpLogRecord.Body); + } } [Fact]