Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<ItemGroup Label="Runtime">
<PackageVersion Include="Microsoft.ApplicationInsights" Version="2.23.0" />
<PackageVersion Include="NLog" Version="5.5.0" />
<PackageVersion Include="System.Text.Json" Version="10.0.0" />
</ItemGroup>
<ItemGroup Label="Test">
<PackageVersion Include="WireMock.Net" Version="1.6.9" />
Expand Down
1 change: 1 addition & 0 deletions src/NLogTarget/NLogTarget.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<ItemGroup>
<PackageReference Include="NLog" />
<PackageReference Include="Microsoft.ApplicationInsights" />
<PackageReference Include="System.Text.Json" />
</ItemGroup>

<Import Project="..\CommonShared\CommonShared.projitems" Label="Shared" />
Expand Down
27 changes: 26 additions & 1 deletion src/NLogTarget/StringDictionaryConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace Microsoft.ApplicationInsights.NLogTarget
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text.Json;

/// <summary>
/// Converts from NLog Object-properties to ApplicationInsight String-properties
Expand Down Expand Up @@ -101,7 +102,31 @@ private static string SafeValueConverter(object value)
{
try
{
return Convert.ToString(value, System.Globalization.CultureInfo.InvariantCulture);
if (value == null)
{
return string.Empty;
}

// Handle primitive types and strings directly
if (value is string str)
{
return str;
}

// Check if the value is a primitive type or a simple type that Convert.ToString handles well
var type = value.GetType();
if (type.IsPrimitive || type.IsEnum || value is decimal || value is DateTime || value is DateTimeOffset || value is Guid || value is TimeSpan)
{
return Convert.ToString(value, System.Globalization.CultureInfo.InvariantCulture);
}

// For complex objects, serialize to JSON
return JsonSerializer.Serialize(value, new JsonSerializerOptions
{
WriteIndented = false,
PropertyNamingPolicy = null,
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.Never
});
}
catch
{
Expand Down
73 changes: 62 additions & 11 deletions src/NLogTarget/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,38 @@
"resolved": "5.5.0",
"contentHash": "FCH8s7GWlonH5JXV9/EpeNJ8pRZQMVZOSWX3JrHPU8rzdHJhS5+lUGGvJIUOtzkGV1clYBFR0WXOI5FnUwVCMA=="
},
"System.Text.Json": {
"type": "Direct",
"requested": "[10.0.0, )",
"resolved": "10.0.0",
"contentHash": "1Dpjwq9peG/Wt5BNbrzIhTpclfOSqBWZsUO28vVr59yQlkvL5jLBWfpfzRmJ1OY+6DciaY0DUcltyzs4fuZHjw==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "10.0.0",
"System.Buffers": "4.6.1",
"System.IO.Pipelines": "10.0.0",
"System.Memory": "4.6.3",
"System.Runtime.CompilerServices.Unsafe": "6.1.2",
"System.Text.Encodings.Web": "10.0.0",
"System.Threading.Tasks.Extensions": "4.6.3"
}
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "10.0.0",
"contentHash": "vFuwSLj9QJBbNR0NeNO4YVASUbokxs+i/xbuu8B+Fs4FAZg5QaFa6eGrMaRqTzzNI5tAb97T7BhSxtLckFyiRA==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.6.3"
}
},
"Microsoft.NETCore.Platforms": {
"type": "Transitive",
"resolved": "1.1.0",
"contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A=="
},
"System.Buffers": {
"type": "Transitive",
"resolved": "4.5.1",
"contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg=="
"resolved": "4.6.1",
"contentHash": "N8GXpmiLMtljq7gwvyS+1QvKT/W2J8sNAvx+HVg4NGmsG/H+2k/y9QI23auLJRterrzCiDH+IWAw4V/GPwsMlw=="
},
"System.Diagnostics.DiagnosticSource": {
"type": "Transitive",
Expand All @@ -45,25 +68,53 @@
"System.Runtime.CompilerServices.Unsafe": "5.0.0"
}
},
"System.IO.Pipelines": {
"type": "Transitive",
"resolved": "10.0.0",
"contentHash": "M1eb3nfXntaRJPrrMVM9EFS8I1bDTnt0uvUS6QP/SicZf/ZZjydMD5NiXxfmwW/uQwaMDP/yX2P+zQN1NBHChg==",
"dependencies": {
"System.Buffers": "4.6.1",
"System.Memory": "4.6.3",
"System.Threading.Tasks.Extensions": "4.6.3"
}
},
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.4",
"contentHash": "1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw==",
"resolved": "4.6.3",
"contentHash": "qdcDOgnFZY40+Q9876JUHnlHu7bosOHX8XISRoH94fwk6hgaeQGSgfZd8srWRZNt5bV9ZW2TljcegDNxsf+96A==",
"dependencies": {
"System.Buffers": "4.5.1",
"System.Numerics.Vectors": "4.4.0",
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
"System.Buffers": "4.6.1",
"System.Numerics.Vectors": "4.6.1",
"System.Runtime.CompilerServices.Unsafe": "6.1.2"
}
},
"System.Numerics.Vectors": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ=="
"resolved": "4.6.1",
"contentHash": "sQxefTnhagrhoq2ReR0D/6K0zJcr9Hrd6kikeXsA1I8kOCboTavcUC4r7TSfpKFeE163uMuxZcyfO1mGO3EN8Q=="
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "ZD9TMpsmYJLrxbbmdvhwt9YEgG5WntEnZ/d1eH8JBX9LBp+Ju8BSBhUGbZMNVHHomWo2KVImJhTDl2hIgw/6MA=="
"resolved": "6.1.2",
"contentHash": "2hBr6zdbIBTDE3EhK7NSVNdX58uTK6iHW/P/Axmm9sl1xoGSLqDvMtpecn226TNwHByFokYwJmt/aQQNlO5CRw=="
},
"System.Text.Encodings.Web": {
"type": "Transitive",
"resolved": "10.0.0",
"contentHash": "257hh1ep1Gqm1Lm0ulxf7vVBVMJuGN6EL4xSWjpi46DffXzm1058IiWsfSC06zSm7SniN+Tb5160UnXsSa8rRg==",
"dependencies": {
"System.Buffers": "4.6.1",
"System.Memory": "4.6.3",
"System.Runtime.CompilerServices.Unsafe": "6.1.2"
}
},
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
"resolved": "4.6.3",
"contentHash": "7sCiwilJLYbTZELaKnc7RecBBXWXA+xMLQWZKWawBxYjp6DBlSE3v9/UcvKBvr1vv2tTOhipiogM8rRmxlhrVA==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "6.1.2"
}
}
}
}
Expand Down
82 changes: 82 additions & 0 deletions test/NLogTarget.Tests/NLogTargetTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,88 @@
Assert.AreEqual("Value", telemetry.Properties["Name"]);
}

[TestMethod]
[TestCategory("NLogTarget")]
public void TraceHasComplexPropertiesSerializedAsJson()
{
var aiLogger = this.CreateTargetWithGivenConnectionString();

var complexObject = new
{
Name = "John Doe",
Age = 30,
Address = new
{
Street = "123 Main St",
City = "New York",
ZipCode = "10001"
},
Tags = new[] { "tag1", "tag2", "tag3" }
};

var eventInfo = new LogEventInfo(LogLevel.Trace, "TestLogger", "Hello!");
eventInfo.Properties["ComplexObject"] = complexObject;
eventInfo.Properties["SimpleString"] = "SimpleValue";
aiLogger.Log(eventInfo);

var telemetry = this.adapterHelper.Channel.SentItems.FirstOrDefault() as TraceTelemetry;
Assert.IsNotNull(telemetry, "Didn't get the log event from the channel");

// Simple string should remain as is
Assert.AreEqual("SimpleValue", telemetry.Properties["SimpleString"]);

// Complex object should be serialized to JSON
Assert.IsTrue(telemetry.Properties.ContainsKey("ComplexObject"), "ComplexObject property not found");
var complexJson = telemetry.Properties["ComplexObject"];

// Verify it's JSON and contains expected properties
Assert.IsTrue(complexJson.Contains("\"Name\""), "JSON should contain Name property");

Check warning on line 271 in test/NLogTarget.Tests/NLogTargetTests.cs

View workflow job for this annotation

GitHub Actions / build

'string.Contains(string)' has a method overload that takes a 'StringComparison' parameter. Replace this call in 'Microsoft.ApplicationInsights.NLogTarget.Tests.ApplicationInsightsTargetTests.TraceHasComplexPropertiesSerializedAsJson()' with a call to 'string.Contains(string, System.StringComparison)' for clarity of intent. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1307)
Assert.IsTrue(complexJson.Contains("\"John Doe\""), "JSON should contain Name value");

Check warning on line 272 in test/NLogTarget.Tests/NLogTargetTests.cs

View workflow job for this annotation

GitHub Actions / build

'string.Contains(string)' has a method overload that takes a 'StringComparison' parameter. Replace this call in 'Microsoft.ApplicationInsights.NLogTarget.Tests.ApplicationInsightsTargetTests.TraceHasComplexPropertiesSerializedAsJson()' with a call to 'string.Contains(string, System.StringComparison)' for clarity of intent. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1307)
Assert.IsTrue(complexJson.Contains("\"Age\""), "JSON should contain Age property");
Assert.IsTrue(complexJson.Contains("30"), "JSON should contain Age value");
Assert.IsTrue(complexJson.Contains("\"Address\""), "JSON should contain Address property");
Assert.IsTrue(complexJson.Contains("\"Street\""), "JSON should contain nested Street property");
Assert.IsTrue(complexJson.Contains("\"123 Main St\""), "JSON should contain nested Street value");
Assert.IsTrue(complexJson.Contains("\"Tags\""), "JSON should contain Tags array");
Assert.IsTrue(complexJson.Contains("\"tag1\""), "JSON should contain array values");
}

[TestMethod]
[TestCategory("NLogTarget")]
public void TraceHandlesVariousPropertyTypes()
{
var aiLogger = this.CreateTargetWithGivenConnectionString();

var eventInfo = new LogEventInfo(LogLevel.Info, "TestLogger", "Testing various types");
eventInfo.Properties["String"] = "Simple string";
eventInfo.Properties["Int"] = 42;
eventInfo.Properties["Bool"] = true;
eventInfo.Properties["Decimal"] = 3.14m;
eventInfo.Properties["DateTime"] = new DateTime(2024, 1, 1, 12, 0, 0, DateTimeKind.Utc);
eventInfo.Properties["Array"] = new[] { 1, 2, 3 };
eventInfo.Properties["Dictionary"] = new Dictionary<string, object> { { "key1", "value1" }, { "key2", 123 } };
eventInfo.Properties["Null"] = null;

aiLogger.Log(eventInfo);

var telemetry = this.adapterHelper.Channel.SentItems.FirstOrDefault() as TraceTelemetry;
Assert.IsNotNull(telemetry, "Didn't get the log event from the channel");

// Simple types should be preserved as strings
Assert.AreEqual("Simple string", telemetry.Properties["String"]);
Assert.AreEqual("42", telemetry.Properties["Int"]);
Assert.AreEqual("True", telemetry.Properties["Bool"]);
Assert.IsTrue(telemetry.Properties["Decimal"].Contains("3.14"), "Decimal value should contain 3.14");

// Complex types should be serialized to JSON
Assert.IsTrue(telemetry.Properties["Array"].Contains("["), "Array should be JSON array");
Assert.IsTrue(telemetry.Properties["Array"].Contains("1"), "Array should contain values");
Assert.IsTrue(telemetry.Properties["Dictionary"].Contains("key1"), "Dictionary should be serialized");
Assert.IsTrue(telemetry.Properties["Dictionary"].Contains("value1"), "Dictionary should contain values");

// Null should be empty string
Assert.AreEqual(string.Empty, telemetry.Properties["Null"]);
}

[TestMethod]
[TestCategory("NLogTarget")]
Expand Down
Loading
Loading