Skip to content

Commit

Permalink
Allow overriding the Part B "name" field value in GenevaLogExporter (#…
Browse files Browse the repository at this point in the history
…1367)

Co-authored-by: Cijo Thomas <cijo.thomas@gmail.com>
  • Loading branch information
jdom and cijothomas committed Sep 22, 2023
1 parent 5cb78d4 commit 5782da1
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 7 deletions.
3 changes: 3 additions & 0 deletions src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

* Allow overriding the Part B "name" field value in GenevaLogExporter.
([#1367](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1367))

## 1.6.0

Released 2023-Sep-09
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,12 +259,9 @@ internal int SerializeLogRecord(LogRecord logRecord)
cursor = MessagePackSerializer.SerializeUInt8(buffer, cursor, GetSeverityNumber(logLevel));
cntFields += 1;

cursor = MessagePackSerializer.SerializeAsciiString(buffer, cursor, "name");
cursor = MessagePackSerializer.SerializeUnicodeString(buffer, cursor, categoryName);
cntFields += 1;

bool hasEnvProperties = false;
bool bodyPopulated = false;
bool namePopulated = false;
for (int i = 0; i < listKvp?.Count; i++)
{
var entry = listKvp[i];
Expand All @@ -285,6 +282,17 @@ internal int SerializeLogRecord(LogRecord logRecord)
if (entry.Value != null)
{
// null is not supported.
if (string.Equals(entry.Key, "name", StringComparison.Ordinal))
{
if (!(entry.Value is string))
{
// name must be string according to Part B in Common Schema. Skip serializing this field otherwise
continue;
}

namePopulated = true;
}

cursor = MessagePackSerializer.SerializeUnicodeString(buffer, cursor, entry.Key);
cursor = MessagePackSerializer.Serialize(buffer, cursor, entry.Value);
cntFields += 1;
Expand All @@ -297,6 +305,13 @@ internal int SerializeLogRecord(LogRecord logRecord)
}
}

if (!namePopulated)
{
cursor = MessagePackSerializer.SerializeAsciiString(buffer, cursor, "name");
cursor = MessagePackSerializer.SerializeUnicodeString(buffer, cursor, categoryName);
cntFields += 1;
}

if (!bodyPopulated && logRecord.FormattedMessage != null)
{
cursor = MessagePackSerializer.SerializeAsciiString(buffer, cursor, "body");
Expand Down
23 changes: 20 additions & 3 deletions src/OpenTelemetry.Exporter.Geneva/TLDExporter/TldLogExporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,6 @@ internal void SerializeLogRecord(LogRecord logRecord)

eb.AddCountedString("severityText", logLevels[(int)logLevel]);
eb.AddUInt8("severityNumber", GetSeverityNumber(logLevel));
eb.AddCountedAnsiString("name", categoryName, Encoding.UTF8);

var eventId = logRecord.EventId;
if (eventId != default)
Expand All @@ -312,6 +311,7 @@ internal void SerializeLogRecord(LogRecord logRecord)

byte hasEnvProperties = 0;
bool bodyPopulated = false;
bool namePopulated = false;

byte partCFieldsCountFromState = 0;
var kvpArrayForPartCFields = partCFields.Value;
Expand Down Expand Up @@ -342,8 +342,20 @@ internal void SerializeLogRecord(LogRecord logRecord)
if (entry.Value != null)
{
// null is not supported.
kvpArrayForPartCFields[partCFieldsCountFromState] = new(entry.Key, entry.Value);
partCFieldsCountFromState++;
if (string.Equals(entry.Key, "name", StringComparison.Ordinal))
{
if (entry.Value is string nameValue)
{
// name must be string according to Part B in Common Schema. Skip serializing this field otherwise
eb.AddCountedAnsiString("name", nameValue, Encoding.UTF8);
namePopulated = true;
}
}
else
{
kvpArrayForPartCFields[partCFieldsCountFromState] = new(entry.Key, entry.Value);
partCFieldsCountFromState++;
}
}
}
else
Expand All @@ -366,6 +378,11 @@ internal void SerializeLogRecord(LogRecord logRecord)
}
}

if (!namePopulated)
{
eb.AddCountedAnsiString("name", categoryName, Encoding.UTF8);
}

if (!bodyPopulated && logRecord.FormattedMessage != null)
{
eb.AddCountedAnsiString("body", logRecord.FormattedMessage, Encoding.UTF8);
Expand Down
136 changes: 136 additions & 0 deletions test/OpenTelemetry.Exporter.Geneva.Tests/GenevaLogExporterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1132,6 +1132,142 @@ public void SerializationTestForEventName(EventNameExportMode eventNameExportMod
}
}

[Theory]
[InlineData(false, false, "Custom name")]
[InlineData(false, false, "")]
[InlineData(false, false, null)]
[InlineData(false, false, 12345)]
[InlineData(true, false, "Custom name")]
[InlineData(true, true, "Custom name")]
[InlineData(true, true, 12345)]
[InlineData(true, false, 12345)]
public void SerializationTestForPartBName(bool hasCustomFields, bool hasNameInCustomFields, object customNameValue)
{
// ARRANGE
string path = string.Empty;
Socket server = null;
var logRecordList = new List<LogRecord>();
try
{
var exporterOptions = new GenevaExporterOptions();

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
exporterOptions.ConnectionString = "EtwSession=OpenTelemetry";
}
else
{
path = GenerateTempFilePath();
exporterOptions.ConnectionString = "Endpoint=unix:" + path;
var endpoint = new UnixDomainSocketEndPoint(path);
server = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.IP);
server.Bind(endpoint);
server.Listen(1);
}

if (hasCustomFields)
{
if (hasNameInCustomFields)
{
exporterOptions.CustomFields = new string[] { "name", "Key1" };
}
else
{
exporterOptions.CustomFields = new string[] { "Key1" };
}
}

using var loggerFactory = LoggerFactory.Create(builder => builder
.AddOpenTelemetry(options =>
{
options.AddGenevaLogExporter(options =>
{
options.ConnectionString = exporterOptions.ConnectionString;
options.CustomFields = exporterOptions.CustomFields;
});
options.AddInMemoryExporter(logRecordList);
}));

// Create a test exporter to get MessagePack byte data to validate if the data was serialized correctly.
using var exporter = new MsgPackLogExporter(exporterOptions);

// Emit a LogRecord and grab a copy of the LogRecord from the collection passed to InMemoryExporter
var logger = loggerFactory.CreateLogger<GenevaLogExporterTests>();

// ACT
// This is treated as structured logging as the state can be converted to IReadOnlyList<KeyValuePair<string, object>>

var state = new List<KeyValuePair<string, object>>()
{
new KeyValuePair<string, object>("Key1", "Value1"),
new KeyValuePair<string, object>("Key2", "Value2"),
};

if (customNameValue != null)
{
state.Add(new KeyValuePair<string, object>("name", customNameValue));
}

logger.Log(
LogLevel.Information,
default,
state,
null,
null);

// VALIDATE
Assert.Single(logRecordList);
var m_buffer = typeof(MsgPackLogExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as ThreadLocal<byte[]>;
_ = exporter.SerializeLogRecord(logRecordList[0]);
object fluentdData = MessagePack.MessagePackSerializer.Deserialize<object>(m_buffer.Value, MessagePack.Resolvers.ContractlessStandardResolver.Instance);
var signal = (fluentdData as object[])[0] as string;
var TimeStampAndMappings = ((fluentdData as object[])[1] as object[])[0];
var mapping = (TimeStampAndMappings as object[])[1] as Dictionary<object, object>;
var actualNameValue = mapping["name"];

if (!hasCustomFields || hasNameInCustomFields)
{
if (customNameValue is string stringNameValue)
{
Assert.Equal(stringNameValue, actualNameValue);
}
else
{
Assert.Equal(typeof(GenevaLogExporterTests).FullName, actualNameValue);
}
}
else
{
Assert.Equal(typeof(GenevaLogExporterTests).FullName, actualNameValue);
if (customNameValue != null)
{
var envProperties = mapping["env_properties"] as Dictionary<object, object>;
if (customNameValue is int customNameNumber)
{
Assert.Equal(Convert.ToInt32(envProperties["name"]), customNameNumber);
}
else
{
Assert.Equal((string)envProperties["name"], (string)customNameValue);
}
}
}

logRecordList.Clear();
}
finally
{
server?.Dispose();
try
{
File.Delete(path);
}
catch
{
}
}
}

[Fact]
public void SerializationTestForEventId()
{
Expand Down

0 comments on commit 5782da1

Please sign in to comment.