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

Add option to set template as body #104

Merged
merged 4 commits into from
Sep 18, 2023
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,7 +2,7 @@
<PropertyGroup>
<Description>This Serilog sink transforms Serilog events into OpenTelemetry
logs and sends them to an OTLP (gRPC or HTTP) endpoint.</Description>
<VersionPrefix>1.0.3</VersionPrefix>
<VersionPrefix>1.1.0</VersionPrefix>
<Authors>Serilog Contributors</Authors>
<TargetFrameworks>net6.0;netstandard2.1;netstandard2.0;net462</TargetFrameworks>
<PackageTags>serilog;sink;opentelemetry</PackageTags>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,20 @@ public enum IncludedData
/// means <c>service.name</c> if not supplied, along with the <c>telemetry.sdk.*</c> group of attributes.
/// </summary>
SpecRequiredResourceAttributes = 16,

/// <summary>
/// Include the log event's message template in the OTLP <c>body</c> instead of the rendered messsage. For
/// example, the string <c>Hello {Name}!</c>.
/// </summary>
/// <remarks>
/// Note: It is often desirable to remove <see cref="IncludedData.MessageTemplateTextAttribute"/> when using
/// <see cref="IncludedData.TemplateBody"/> but otherwise use defaults.
/// <code>
/// .WriteTo.OpenTelemetry(options =>
/// {
/// options.IncludedData = (options.IncludedData | IncludedData.TemplateBody) &amp; ~IncludedData.MessageTemplateTextAttribute;
/// })
/// </code>
/// </remarks>
TemplateBody = 32
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,33 @@ public static LogRecord ToLogRecord(LogEvent logEvent, IFormatProvider? formatPr

ProcessProperties(logRecord, logEvent);
ProcessTimestamp(logRecord, logEvent);
ProcessMessage(logRecord, logEvent, formatProvider);
ProcessMessage(logRecord, logEvent, includedFields, formatProvider);
ProcessLevel(logRecord, logEvent);
ProcessException(logRecord, logEvent);
ProcessIncludedFields(logRecord, logEvent, includedFields, activityContextCollector);

return logRecord;
}

public static void ProcessMessage(LogRecord logRecord, LogEvent logEvent, IFormatProvider? formatProvider)
public static void ProcessMessage(LogRecord logRecord, LogEvent logEvent, IncludedData includedFields, IFormatProvider? formatProvider)
{
var renderedMessage = CleanMessageTemplateFormatter.Format(logEvent.MessageTemplate, logEvent.Properties, formatProvider);
if (renderedMessage.Trim() != "")
if (!includedFields.HasFlag(IncludedData.TemplateBody))
{
var renderedMessage = CleanMessageTemplateFormatter.Format(logEvent.MessageTemplate, logEvent.Properties, formatProvider);

if (renderedMessage.Trim() != "")
{
logRecord.Body = new AnyValue
{
StringValue = renderedMessage
};
}
}
else if (includedFields.HasFlag(IncludedData.TemplateBody) && logEvent.MessageTemplate.Text.Trim() != "")
{
logRecord.Body = new AnyValue
{
StringValue = renderedMessage
StringValue = logEvent.MessageTemplate.Text
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class OpenTelemetrySinkOptions
internal const string DefaultEndpoint = "http://localhost:4317";
internal const OtlpProtocol DefaultProtocol = OtlpProtocol.Grpc;

const IncludedData DefaultIncludedData = IncludedData.MessageTemplateTextAttribute |
internal const IncludedData DefaultIncludedData = IncludedData.MessageTemplateTextAttribute |
IncludedData.TraceIdField | IncludedData.SpanIdField |
IncludedData.SpecRequiredResourceAttributes;

Expand Down
36 changes: 25 additions & 11 deletions test/Serilog.Sinks.OpenTelemetry.Tests/LogRecordBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ public void TestProcessMessage()
{
var logRecord = new LogRecord();

LogRecordBuilder.ProcessMessage(logRecord, Some.SerilogEvent(messageTemplate: ""), null);
LogRecordBuilder.ProcessMessage(logRecord, Some.SerilogEvent(messageTemplate: ""), OpenTelemetrySinkOptions.DefaultIncludedData, null);
Assert.Null(logRecord.Body);

LogRecordBuilder.ProcessMessage(logRecord, Some.SerilogEvent(messageTemplate: "\t\f "), null);
LogRecordBuilder.ProcessMessage(logRecord, Some.SerilogEvent(messageTemplate: "\t\f "), OpenTelemetrySinkOptions.DefaultIncludedData, null);
Assert.Null(logRecord.Body);

const string message = "log message";
LogRecordBuilder.ProcessMessage(logRecord, Some.SerilogEvent(messageTemplate: message), null);
LogRecordBuilder.ProcessMessage(logRecord, Some.SerilogEvent(messageTemplate: message), OpenTelemetrySinkOptions.DefaultIncludedData, null);
Assert.NotNull(logRecord.Body);
Assert.Equal(message, logRecord.Body.StringValue);
}
Expand All @@ -45,7 +45,7 @@ public void TestProcessMessage()
public void TestProcessLevel()
{
var logRecord = new LogRecord();
var logEvent = Some.SerilogEvent();
var logEvent = Some.DefaultSerilogEvent();

LogRecordBuilder.ProcessLevel(logRecord, logEvent);

Expand All @@ -57,7 +57,7 @@ public void TestProcessLevel()
public void TestProcessProperties()
{
var logRecord = new LogRecord();
var logEvent = Some.SerilogEvent();
var logEvent = Some.DefaultSerilogEvent();

var prop = new LogEventProperty("property_name", new ScalarValue("ok"));
var propertyKeyValue = PrimitiveConversions.NewStringAttribute("property_name", "ok");
Expand All @@ -76,7 +76,7 @@ public void TestTimestamp()
var nowNano = PrimitiveConversions.ToUnixNano(now);

var logRecord = new LogRecord();
var logEvent = Some.SerilogEvent(timestamp: now);
var logEvent = Some.SerilogEvent(Some.TestMessageTemplate, timestamp: now);

LogRecordBuilder.ProcessTimestamp(logRecord, logEvent);

Expand All @@ -96,7 +96,7 @@ public void TestException()
catch (Exception ex)
{
var logRecord = new LogRecord();
var logEvent = Some.SerilogEvent(ex: ex);
var logEvent = Some.SerilogEvent(Some.TestMessageTemplate, ex: ex);

LogRecordBuilder.ProcessException(logRecord, logEvent);

Expand Down Expand Up @@ -131,11 +131,14 @@ public void IncludeMessageTemplateMD5Hash()
[Fact]
public void IncludeMessageTemplateText()
{
var logEvent = Some.SerilogEvent(messageTemplate: Some.TestMessageTemplate);
var messageTemplate = "Hello, {Name}";
var properties = new List<LogEventProperty> { new("Name", new ScalarValue("World")) };

var logEvent = Some.SerilogEvent(messageTemplate, properties);

var logRecord = LogRecordBuilder.ToLogRecord(logEvent, null, IncludedData.MessageTemplateTextAttribute, new());

var expectedAttribute = new KeyValue { Key = SemanticConventions.AttributeMessageTemplateText, Value = new() { StringValue = Some.TestMessageTemplate }};
var expectedAttribute = new KeyValue { Key = SemanticConventions.AttributeMessageTemplateText, Value = new() { StringValue = messageTemplate } };
Assert.Contains(expectedAttribute, logRecord.Attributes);
}

Expand All @@ -146,7 +149,7 @@ public void IncludeTraceIdWhenActivityIsNull()

var collector = new ActivityContextCollector();

var logEvent = Some.SerilogEvent();
var logEvent = Some.DefaultSerilogEvent();
collector.CollectFor(logEvent);

var logRecord = LogRecordBuilder.ToLogRecord(logEvent, null, IncludedData.TraceIdField | IncludedData.SpanIdField, collector);
Expand All @@ -172,12 +175,23 @@ public void IncludeTraceIdAndSpanId()

var collector = new ActivityContextCollector();

var logEvent = Some.SerilogEvent();
var logEvent = Some.DefaultSerilogEvent();
collector.CollectFor(logEvent);

var logRecord = LogRecordBuilder.ToLogRecord(logEvent, null, IncludedData.TraceIdField | IncludedData.SpanIdField, collector);

Assert.Equal(logRecord.TraceId, PrimitiveConversions.ToOpenTelemetryTraceId(Activity.Current.TraceId.ToHexString()));
Assert.Equal(logRecord.SpanId, PrimitiveConversions.ToOpenTelemetrySpanId(Activity.Current.SpanId.ToHexString()));
}

[Fact]
public void TemplateBodyIncludesMessageTemplateInBody()
{
var messageTemplate = "Hello, {Name}";
var properties = new List<LogEventProperty> { new("Name", new ScalarValue("World")) };

var logRecord = LogRecordBuilder.ToLogRecord(Some.SerilogEvent(messageTemplate, properties), null, IncludedData.TemplateBody, new());
Assert.NotNull(logRecord.Body);
Assert.Equal(messageTemplate, logRecord.Body.StringValue);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps for the test name, TemplateBodyIncludesMessageTemplateInBody()? (A bit painfully verbose but spells out the goal.)

For the Some.SerilogEvent(), the test could end up inconclusive if the sample event has a template with no holes (so the test wouldn't be able to tell the difference between the template and the rendered message). Does the SerilogEvent() method allow specifying the template, e.g.:

Some.SerilogEvent("Hello, {Name}", "world")

or could we find a way to ensure this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean to be explicit about the template? Some.SerilogEvent() defines a default template of "Message template {Variable}". If the default was changed to something like Message template then this test would not fail if the underlying implementation "broke". Is that what you are getting at?

In my opinion Some.SerilogEvent() is a bit odd because it lets you override the template but not the properties. I am thinking the best way to handle this might be to refactor the tests a bit. Something like

- internal static LogEvent SerilogEvent(DateTimeOffset? timestamp = null, Exception? ex = null, string messageTemplate = TestMessageTemplate)
+ internal static LogEvent DefaultSerilogEvent()
+ internal static LogEvent SerilogEvent(DateTimeOffset? timestamp = null, Exception? ex = null, string messageTemplate, IEnumerable<LogEventProperty> properties)

Thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks good to me 👍

(Sorry about the slow reply, spent a few nights away over the weekend, just catching up now 😅 )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made some updates, let me know what you think.

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class RequestTemplateFactoryTests
// request template to another.
public void TestNoDuplicateLogs()
{
var logEvent = Some.SerilogEvent();
var logEvent = Some.DefaultSerilogEvent();
var logRecord = LogRecordBuilder.ToLogRecord(logEvent, null, IncludedData.None, new());

var requestTemplate = RequestTemplateFactory.CreateRequestTemplate(new Dictionary<string, object>());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ namespace Serilog.Sinks.OpenTelemetry
TraceIdField = 4,
SpanIdField = 8,
SpecRequiredResourceAttributes = 16,
TemplateBody = 32,
}
public class OpenTelemetrySinkOptions
{
Expand Down
18 changes: 16 additions & 2 deletions test/Serilog.Sinks.OpenTelemetry.Tests/Some.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,21 @@ static class Some

public const string TestMessageTemplate = "Message template {Variable}";

internal static LogEvent SerilogEvent(DateTimeOffset? timestamp = null, Exception? ex = null, string messageTemplate = TestMessageTemplate)
internal static LogEvent DefaultSerilogEvent()
{
return SerilogEvent(
TestMessageTemplate,
new List<LogEventProperty> { new("Variable", new ScalarValue(42)) },
DateTimeOffset.UtcNow,
null);
}

internal static LogEvent SerilogEvent(string messageTemplate, DateTimeOffset? timestamp = null, Exception? ex = null)
{
return SerilogEvent(messageTemplate, new List<LogEventProperty>(), timestamp, ex);
}

internal static LogEvent SerilogEvent(string messageTemplate, IEnumerable<LogEventProperty> properties, DateTimeOffset? timestamp = null, Exception? ex = null)
{
var ts = timestamp ?? DateTimeOffset.UtcNow;
var parser = new MessageTemplateParser();
Expand All @@ -33,7 +47,7 @@ internal static LogEvent SerilogEvent(DateTimeOffset? timestamp = null, Exceptio
LogEventLevel.Warning,
ex,
template,
new List<LogEventProperty>{ new("Variable", new ScalarValue(42)) });
properties);

return logEvent;
}
Expand Down