Skip to content

Commit b1b0738

Browse files
authored
Fix Serilog direct log submission failing when writing unknown custom properties (#8010)
## Summary of changes This fixes a `JsonWriterException` that we were hitting when attempting to serialize custom properties in Serilog log messages when we had Direct Log Submission enabled. This would then result in direct log submission to stop working. ## Reason for change When writing a custom property in Serilog we only write the JSON when Debug logging is enabled, when it isn't enabled we actually end up putting the writer into an invalid state, we then hit the following exception: ``` 2025-12-23 16:04:16.209 -05:00 [ERR] An error occured sending logs to Datadog Datadog.Trace.Vendors.Newtonsoft.Json.JsonWriterException: Token PropertyName in state Property would result in an invalid JSON object. Path ''.    at Datadog.Trace.Vendors.Newtonsoft.Json.JsonWriter.AutoComplete(JsonToken tokenBeingWritten) in C:\Users\steven.bouwkamp\source\repos\dd-trace-dotnet2\tracer\src\Datadog.Trace\Vendors\Newtonsoft.Json\JsonWriter.cs:line 880    at Datadog.Trace.Vendors.Newtonsoft.Json.JsonWriter.InternalWritePropertyName(String name) in C:\Users\steven.bouwkamp\source\repos\dd-trace-dotnet2\tracer\src\Datadog.Trace\Vendors\Newtonsoft.Json\JsonWriter.cs:line 1760    at Datadog.Trace.Vendors.Newtonsoft.Json.JsonTextWriter.WritePropertyName(String name, Boolean escape) in C:\Users\steven.bouwkamp\source\repos\dd-trace-dotnet2\tracer\src\Datadog.Trace\Vendors\Newtonsoft.Json\JsonTextWriter.cs:line 291    at Datadog.Trace.Logging.DirectSubmission.Formatting.LogFormatter.FormatLog[T](StringBuilder builder, T& state, DateTime timestamp, String message, Nullable`1 eventId, String logLevel, Exception exception, FormatDelegate`1 renderPropertiesDelegate) in C:\Users\steven.bouwkamp\source\repos\dd-trace-dotnet2\tracer\src\Datadog.Trace\Logging\DirectSubmission\Formatting\LogFormatter.cs:line 335    at Datadog.Trace.ClrProfiler.AutoInstrumentation.Logging.Serilog.DirectSubmission.Formatting.SerilogLogFormatter.FormatLogEvent(LogFormatter logFormatter, StringBuilder sb, ILogEvent logEvent) in C:\Users\steven.bouwkamp\source\repos\dd-trace-dotnet2\tracer\src\Datadog.Trace\ClrProfiler\AutoInstrumentation\Logging\Serilog\DirectSubmission\Formatting\SerilogLogFormatter.cs:line 26    at Datadog.Trace.ClrProfiler.AutoInstrumentation.Logging.Serilog.DirectSubmission.SerilogDirectSubmissionLogEvent.Format(StringBuilder sb, LogFormatter formatter) in C:\Users\steven.bouwkamp\source\repos\dd-trace-dotnet2\tracer\src\Datadog.Trace\ClrProfiler\AutoInstrumentation\Logging\Serilog\DirectSubmission\SerilogDirectSubmissionLogEvent.cs:line 25    at Datadog.Trace.Logging.DirectSubmission.Sink.DirectSubmissionLogSink.EmitBatch(Queue`1 events) in C:\Users\steven.bouwkamp\source\repos\dd-trace-dotnet2\tracer\src\Datadog.Trace\Logging\DirectSubmission\Sink\DirectSubmissionLogSink.cs:line 103  { MachineName: ".", Process: "[100360 dotnet]", AppDomain: "[1 LogsInjection.Serilog]", AssemblyLoadContext: "\"\" Datadog.Trace.ClrProfiler.Managed.Loader.ManagedProfilerAssemblyLoadContext #1", TracerVersion: "3.35.0.0" } ``` From what I can tell this causes direct log submission to stop working. I've seen this in Error Tracking and it may be related to a customer escalation regarding issues with Serilog direct log submission to seemingly stop working. ## Implementation details ## Test coverage I've reproduced this, but it is strange, I guess Serilog doesn't actually support this? I don't understand completely what was going on, but if I added a custom property and enriched all logs with it but did _not_ have direct log submission enabled there would be no logs found in the tests but they did make it to the file. 😕 ## Other details <!-- Fixes #{issue} --> I only toggled this custom property on when direct log submission is enabled due to the above Test Coverage note. <!-- ⚠️ Note: Where possible, please obtain 2 approvals prior to merging. Unless CODEOWNERS specifies otherwise, for external teams it is typically best to have one review from a team member, and one review from apm-dotnet. Trivial changes do not require 2 reviews. MergeQueue is NOT enabled in this repository. If you have write access to the repo, the PR has 1-2 approvals (see above), and all of the required checks have passed, you can use the Squash and Merge button to merge the PR. If you don't have write access, or you need help, reach out in the #apm-dotnet channel in Slack. -->
1 parent e2908cf commit b1b0738

File tree

2 files changed

+52
-3
lines changed
  • tracer
    • src/Datadog.Trace/ClrProfiler/AutoInstrumentation/Logging/Serilog/DirectSubmission/Formatting
    • test/test-applications/integrations/LogsInjection.Serilog

2 files changed

+52
-3
lines changed

tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/Logging/Serilog/DirectSubmission/Formatting/SerilogLogFormatter.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// <copyright file="SerilogLogFormatter.cs" company="Datadog">
1+
// <copyright file="SerilogLogFormatter.cs" company="Datadog">
22
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
33
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
44
// </copyright>
@@ -107,10 +107,12 @@ private static void FormatLogEventPropertyValue(JsonTextWriter writer, object va
107107
return;
108108
}
109109

110+
// Always write null for unknown types to maintain valid JSON
111+
LogFormatter.WriteValue(writer, value: null);
112+
110113
if (Log.IsEnabled(LogEventLevel.Debug))
111114
{
112-
Log.Debug("Unknown Serilog LogEventPropertyValue '{Type}': skipping in log message", value.GetType());
113-
LogFormatter.WriteValue(writer, value: null);
115+
Log.Debug("Unknown Serilog LogEventPropertyValue '{Type}': writing null value", value.GetType());
114116
}
115117
}
116118

tracer/test/test-applications/integrations/LogsInjection.Serilog/Program.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,42 @@
1515

1616
namespace LogsInjection.Serilog
1717
{
18+
/// <summary>
19+
/// Custom LogEventPropertyValue that doesn't match any of the duck types
20+
/// (ScalarValueDuck, SequenceValueDuck, StructureValueDuck, DictionaryValueDuck).
21+
/// This reproduces the bug where FormatLogEventPropertyValue doesn't write a value
22+
/// when debug logging is disabled.
23+
/// </summary>
24+
public class CustomPropertyValue : LogEventPropertyValue
25+
{
26+
private readonly object _value;
27+
28+
public CustomPropertyValue(object value)
29+
{
30+
_value = value;
31+
}
32+
33+
public override void Render(TextWriter output, string format = null, IFormatProvider formatProvider = null)
34+
{
35+
output.Write(_value?.ToString() ?? "null");
36+
}
37+
}
38+
39+
/// <summary>
40+
/// Custom enricher that adds a property with our custom value type.
41+
/// This will trigger the bug in SerilogLogFormatter.FormatLogEventPropertyValue
42+
/// when debug logging is disabled.
43+
/// </summary>
44+
public class CustomPropertyEnricher : ILogEventEnricher
45+
{
46+
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
47+
{
48+
// This creates a property with our custom value type
49+
var customProperty = new LogEventProperty("CustomField", new CustomPropertyValue("test-value"));
50+
logEvent.AddPropertyIfAbsent(customProperty);
51+
}
52+
}
53+
1854
public static class Program
1955
{
2056
public static int Main(string[] args)
@@ -81,6 +117,17 @@ public static int Main(string[] args)
81117
.WriteTo.Logger(lc => lc.WriteTo.Console());
82118
}
83119

120+
// Only enable the custom enricher when direct log submission is active
121+
// This tests handling of unknown Serilog property types in SerilogLogFormatter
122+
// without breaking regular Serilog file sinks (which can't serialize custom types)
123+
var directLogSubmissionEnabled = !string.IsNullOrEmpty(
124+
Environment.GetEnvironmentVariable("DD_DIRECT_LOG_SUBMISSION_ENABLED_INTEGRATIONS"));
125+
126+
if (directLogSubmissionEnabled)
127+
{
128+
configuration = configuration.Enrich.With(new CustomPropertyEnricher());
129+
}
130+
84131
var log = configuration.CreateLogger();
85132

86133
#if SERILOG_2_12

0 commit comments

Comments
 (0)