Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix severity mapping for OTLP Log Exporter #3177

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

* LogExporter to correcly map Severity to OTLP.
([#3177](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3177))

## 1.2.0-rc5

Released 2022-Apr-12
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using System.Runtime.CompilerServices;
using Google.Protobuf;
using Google.Protobuf.Collections;
using Microsoft.Extensions.Logging;
using OpenTelemetry.Internal;
using OpenTelemetry.Logs;
using OpenTelemetry.Trace;
Expand All @@ -30,12 +31,17 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation
{
internal static class LogRecordExtensions
{
private static readonly string[] LogLevels = new string[7]
{
"Trace", "Debug", "Information", "Warning", "Error", "Critical", "None",
};

internal static void AddBatch(
this OtlpCollector.ExportLogsServiceRequest request,
OtlpResource.Resource processResource,
in Batch<LogRecord> logRecordBatch)
{
OtlpLogs.ResourceLogs resourceLogs = new OtlpLogs.ResourceLogs
var resourceLogs = new OtlpLogs.ResourceLogs
{
Resource = processResource,
};
Expand Down Expand Up @@ -64,10 +70,8 @@ internal static OtlpLogs.LogRecord ToOtlpLog(this LogRecord logRecord)
otlpLogRecord = new OtlpLogs.LogRecord
{
TimeUnixNano = (ulong)logRecord.Timestamp.ToUnixTimeNanoseconds(),

// TODO: Devise mapping of LogLevel to SeverityNumber
// See: https://github.com/open-telemetry/opentelemetry-proto/blob/bacfe08d84e21fb2a779e302d12e8dfeb67e7b86/opentelemetry/proto/logs/v1/logs.proto#L100-L102
SeverityText = logRecord.LogLevel.ToString(),
SeverityNumber = GetSeverityNumber(logRecord.LogLevel),
SeverityText = LogLevels[(int)logRecord.LogLevel],
};

// TODO: Add logRecord.CategoryName as an attribute
Expand Down Expand Up @@ -144,5 +148,37 @@ private static void AddIntAttribute(this RepeatedField<OtlpCommon.KeyValue> repe
Value = new OtlpCommon.AnyValue { IntValue = value },
});
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static OtlpLogs.SeverityNumber GetSeverityNumber(LogLevel logLevel)
{
// Maps the ILogger LogLevel to OpenTelemetry logging level.
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/data-model.md#appendix-b-severitynumber-example-mappings
// TODO: for improving perf simply do ((int)loglevel * 4) + 1
// or ((int)logLevel << 2) + 1
// Current code is just for ease of reading.
switch (logLevel)
{
case LogLevel.Trace:
return OtlpLogs.SeverityNumber.Trace;
case LogLevel.Debug:
return OtlpLogs.SeverityNumber.Debug;
case LogLevel.Information:
return OtlpLogs.SeverityNumber.Info;
case LogLevel.Warning:
return OtlpLogs.SeverityNumber.Warn;
case LogLevel.Error:
return OtlpLogs.SeverityNumber.Error;
case LogLevel.Critical:
return OtlpLogs.SeverityNumber.Fatal;

// TODO:
// we reach default only for LogLevel.None
// but that is filtered out anyway.
// should we throw here then?
default:
return OtlpLogs.SeverityNumber.Debug;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
using OpenTelemetry.Tests;
using OpenTelemetry.Trace;
using Xunit;
using OtlpLogs = Opentelemetry.Proto.Logs.V1;

namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests
{
Expand All @@ -32,7 +33,7 @@ public class OtlpLogExporterTests : Http2UnencryptedSupportTests
[Fact]
public void CheckToOtlpLogRecordStateValues()
{
List<LogRecord> logRecords = new List<LogRecord>();
var logRecords = new List<LogRecord>();
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddOpenTelemetry(options =>
Expand Down Expand Up @@ -61,7 +62,7 @@ public void CheckToOtlpLogRecordStateValues()
[Fact]
public void CheckToOtlpLogRecordEventId()
{
List<LogRecord> logRecords = new List<LogRecord>();
var logRecords = new List<LogRecord>();
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddOpenTelemetry(options =>
Expand Down Expand Up @@ -110,7 +111,7 @@ public void CheckToOtlpLogRecordEventId()
[Fact]
public void CheckToOtlpLogRecordTraceIdSpanIdFlagWithNoActivity()
{
List<LogRecord> logRecords = new List<LogRecord>();
var logRecords = new List<LogRecord>();
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddOpenTelemetry(options =>
Expand All @@ -133,7 +134,7 @@ public void CheckToOtlpLogRecordTraceIdSpanIdFlagWithNoActivity()
[Fact]
public void CheckToOtlpLogRecordSpanIdTraceIdAndFlag()
{
List<LogRecord> logRecords = new List<LogRecord>();
var logRecords = new List<LogRecord>();
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddOpenTelemetry(options =>
Expand Down Expand Up @@ -162,34 +163,62 @@ public void CheckToOtlpLogRecordSpanIdTraceIdAndFlag()
Assert.Equal((uint)logRecord.TraceFlags, otlpLogRecord.Flags);
}

[Fact]
public void CheckToOtlpLogRecordSeverityText()
[Theory]
[InlineData(LogLevel.Trace)]
[InlineData(LogLevel.Debug)]
[InlineData(LogLevel.Information)]
[InlineData(LogLevel.Warning)]
[InlineData(LogLevel.Error)]
[InlineData(LogLevel.Critical)]
public void CheckToOtlpLogRecordSeverityLevelAndText(LogLevel logLevel)
{
List<LogRecord> logRecords = new List<LogRecord>();
var logRecords = new List<LogRecord>();
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddOpenTelemetry(options =>
{
options.AddInMemoryExporter(logRecords);
options.IncludeFormattedMessage = true;
});
})
.AddFilter("CheckToOtlpLogRecordSeverityLevelAndText", LogLevel.Trace);
});

var logger = loggerFactory.CreateLogger("OtlpLogExporterTests");
logger.LogInformation("Hello from {name} {price}.", "tomato", 2.99);
var logger = loggerFactory.CreateLogger("CheckToOtlpLogRecordSeverityLevelAndText");
logger.Log(logLevel, "Hello from {name} {price}.", "tomato", 2.99);
Assert.Single(logRecords);

var logRecord = logRecords[0];
var otlpLogRecord = logRecord.ToOtlpLog();

Assert.NotNull(otlpLogRecord);
Assert.Equal(logRecord.LogLevel.ToString(), otlpLogRecord.SeverityText);
switch (logLevel)
{
case LogLevel.Trace:
Assert.Equal(OtlpLogs.SeverityNumber.Trace, otlpLogRecord.SeverityNumber);
break;
case LogLevel.Debug:
Assert.Equal(OtlpLogs.SeverityNumber.Debug, otlpLogRecord.SeverityNumber);
break;
case LogLevel.Information:
Assert.Equal(OtlpLogs.SeverityNumber.Info, otlpLogRecord.SeverityNumber);
break;
case LogLevel.Warning:
Assert.Equal(OtlpLogs.SeverityNumber.Warn, otlpLogRecord.SeverityNumber);
break;
case LogLevel.Error:
Assert.Equal(OtlpLogs.SeverityNumber.Error, otlpLogRecord.SeverityNumber);
break;
case LogLevel.Critical:
Assert.Equal(OtlpLogs.SeverityNumber.Fatal, otlpLogRecord.SeverityNumber);
break;
}
}

[Fact]
public void CheckToOtlpLogRecordFormattedMessage()
{
List<LogRecord> logRecords = new List<LogRecord>();
var logRecords = new List<LogRecord>();
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddOpenTelemetry(options =>
Expand All @@ -213,7 +242,7 @@ public void CheckToOtlpLogRecordFormattedMessage()
[Fact]
public void CheckToOtlpLogRecordExceptionAttributes()
{
List<LogRecord> logRecords = new List<LogRecord>();
var logRecords = new List<LogRecord>();
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddOpenTelemetry(options =>
Expand Down