This is an automated email from the ASF dual-hosted git repository.
curth pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git
The following commit(s) were added to refs/heads/main by this push:
new e8642f824 feat(csharp/src/Telemetry): re-enable compile-time JSON
serializer context for trace activity (#4013)
e8642f824 is described below
commit e8642f824393a99d4a2ac3cde89f15b10e7f3101
Author: Bruce Irschick <[email protected]>
AuthorDate: Tue Feb 24 09:19:53 2026 -0800
feat(csharp/src/Telemetry): re-enable compile-time JSON serializer context
for trace activity (#4013)
Re-enables compile time JSON serializer context for the
SerializableActivity class.
- The context include a number of well-known data types
- The serializer now includes an option to handle unknown types with the
default, reflection-based serialization
---
.../Traces/Exporters/FileExporter/FileExporter.cs | 11 +-
.../Listeners/FileListener/ActivityProcessor.cs | 11 +-
.../SerializableActivitySerializerContext.cs | 61 +++++++
.../FileListener/SerializableActivityTests.cs | 188 +++++++++++++++++++++
4 files changed, 269 insertions(+), 2 deletions(-)
diff --git a/csharp/src/Telemetry/Traces/Exporters/FileExporter/FileExporter.cs
b/csharp/src/Telemetry/Traces/Exporters/FileExporter/FileExporter.cs
index 08d4336ab..90c69abd9 100644
--- a/csharp/src/Telemetry/Traces/Exporters/FileExporter/FileExporter.cs
+++ b/csharp/src/Telemetry/Traces/Exporters/FileExporter/FileExporter.cs
@@ -21,6 +21,7 @@ using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.Json;
+using System.Text.Json.Serialization.Metadata;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
@@ -38,6 +39,12 @@ namespace
Apache.Arrow.Adbc.Telemetry.Traces.Exporters.FileExporter
private static readonly ConcurrentDictionary<string,
Lazy<FileExporterInstance>> s_fileExporters = new();
private static readonly byte[] s_newLine =
Encoding.UTF8.GetBytes(Environment.NewLine);
+ private static readonly JsonSerializerOptions s_serializerOptions =
new()
+ {
+ TypeInfoResolver = JsonTypeInfoResolver.Combine(
+ SerializableActivitySerializerContext.Default,
+ new DefaultJsonTypeInfoResolver())
+ };
private readonly TracingFile _tracingFile;
private readonly string _fileBaseName;
@@ -151,7 +158,9 @@ namespace
Apache.Arrow.Adbc.Telemetry.Traces.Exporters.FileExporter
SerializableActivity serializableActivity = new(activity);
await JsonSerializer.SerializeAsync(
stream,
- serializableActivity, cancellationToken:
cancellationToken).ConfigureAwait(false);
+ serializableActivity,
+ s_serializerOptions,
+ cancellationToken:
cancellationToken).ConfigureAwait(false);
stream.Write(s_newLine, 0, s_newLine.Length);
stream.Position = 0;
diff --git
a/csharp/src/Telemetry/Traces/Listeners/FileListener/ActivityProcessor.cs
b/csharp/src/Telemetry/Traces/Listeners/FileListener/ActivityProcessor.cs
index 20fff5840..f34c79bfc 100644
--- a/csharp/src/Telemetry/Traces/Listeners/FileListener/ActivityProcessor.cs
+++ b/csharp/src/Telemetry/Traces/Listeners/FileListener/ActivityProcessor.cs
@@ -20,6 +20,7 @@ using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.Json;
+using System.Text.Json.Serialization.Metadata;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
@@ -29,6 +30,12 @@ namespace
Apache.Arrow.Adbc.Telemetry.Traces.Listeners.FileListener
internal sealed class ActivityProcessor : IDisposable
{
private static readonly byte[] s_newLine =
Encoding.UTF8.GetBytes(Environment.NewLine);
+ private static readonly JsonSerializerOptions s_serializerOptions =
new()
+ {
+ TypeInfoResolver = JsonTypeInfoResolver.Combine(
+ SerializableActivitySerializerContext.Default,
+ new DefaultJsonTypeInfoResolver())
+ };
private Task? _processingTask;
private readonly Channel<Activity> _channel;
private readonly Func<Stream, CancellationToken, Task>
_streamWriterFunc;
@@ -91,7 +98,9 @@ namespace
Apache.Arrow.Adbc.Telemetry.Traces.Listeners.FileListener
SerializableActivity serializableActivity = new(activity);
await JsonSerializer.SerializeAsync(
stream,
- serializableActivity, cancellationToken:
cancellationToken).ConfigureAwait(false);
+ serializableActivity,
+ s_serializerOptions,
+ cancellationToken:
cancellationToken).ConfigureAwait(false);
stream.Write(s_newLine, 0, s_newLine.Length);
stream.Position = 0;
diff --git
a/csharp/src/Telemetry/Traces/Listeners/FileListener/SerializableActivitySerializerContext.cs
b/csharp/src/Telemetry/Traces/Listeners/FileListener/SerializableActivitySerializerContext.cs
new file mode 100644
index 000000000..7e503bb52
--- /dev/null
+++
b/csharp/src/Telemetry/Traces/Listeners/FileListener/SerializableActivitySerializerContext.cs
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Text.Json.Serialization;
+
+namespace Apache.Arrow.Adbc.Telemetry.Traces.Listeners.FileListener
+{
+ [JsonSerializable(typeof(SerializableActivity))]
+
+ [JsonSerializable(typeof(string))]
+ [JsonSerializable(typeof(byte))]
+ [JsonSerializable(typeof(sbyte))]
+ [JsonSerializable(typeof(ushort))]
+ [JsonSerializable(typeof(short))]
+ [JsonSerializable(typeof(uint))]
+ [JsonSerializable(typeof(int))]
+ [JsonSerializable(typeof(ulong))]
+ [JsonSerializable(typeof(long))]
+ [JsonSerializable(typeof(ulong))]
+ [JsonSerializable(typeof(float))]
+ [JsonSerializable(typeof(double))]
+ [JsonSerializable(typeof(decimal))]
+ [JsonSerializable(typeof(char))]
+ [JsonSerializable(typeof(bool))]
+
+ [JsonSerializable(typeof(string[]))]
+ [JsonSerializable(typeof(byte[]))]
+ [JsonSerializable(typeof(sbyte[]))]
+ [JsonSerializable(typeof(ushort[]))]
+ [JsonSerializable(typeof(short[]))]
+ [JsonSerializable(typeof(uint[]))]
+ [JsonSerializable(typeof(int[]))]
+ [JsonSerializable(typeof(ulong[]))]
+ [JsonSerializable(typeof(long[]))]
+ [JsonSerializable(typeof(ulong[]))]
+ [JsonSerializable(typeof(float[]))]
+ [JsonSerializable(typeof(double[]))]
+ [JsonSerializable(typeof(decimal[]))]
+ [JsonSerializable(typeof(char[]))]
+ [JsonSerializable(typeof(bool[]))]
+
+ [JsonSerializable(typeof(Uri))]
+ internal partial class SerializableActivitySerializerContext :
JsonSerializerContext
+ {
+ }
+}
diff --git
a/csharp/test/Telemetry/Traces/Listeners/FileListener/SerializableActivityTests.cs
b/csharp/test/Telemetry/Traces/Listeners/FileListener/SerializableActivityTests.cs
new file mode 100644
index 000000000..d8e03c6fa
--- /dev/null
+++
b/csharp/test/Telemetry/Traces/Listeners/FileListener/SerializableActivityTests.cs
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Diagnostics;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization.Metadata;
+using Apache.Arrow.Adbc.Telemetry.Traces.Listeners.FileListener;
+using Xunit.Abstractions;
+
+namespace Apache.Arrow.Adbc.Tests.Telemetry.Traces.Listeners.FileListener
+{
+ public class SerializableActivityTests
+ {
+ private readonly ITestOutputHelper _output;
+
+ public class SerializableActivityTestData : TheoryData<Activity>
+ {
+ public SerializableActivityTestData()
+ {
+ var activityWithTags = new Activity("TestActivityWithTags");
+ int index = 0;
+
+ activityWithTags.AddTag("key" + index++, "value1");
+ activityWithTags.AddTag("key" + index++, (sbyte)123);
+ activityWithTags.AddTag("key" + index++, (byte)123);
+ activityWithTags.AddTag("key" + index++, (short)123);
+ activityWithTags.AddTag("key" + index++, (ushort)123);
+ activityWithTags.AddTag("key" + index++, (int)123);
+ activityWithTags.AddTag("key" + index++, (uint)123);
+ activityWithTags.AddTag("key" + index++, (long)123);
+ activityWithTags.AddTag("key" + index++, (ulong)123);
+ activityWithTags.AddTag("key" + index++, (float)123);
+ activityWithTags.AddTag("key" + index++, (double)123);
+ activityWithTags.AddTag("key" + index++, (decimal)123);
+ activityWithTags.AddTag("key" + index++, true);
+ activityWithTags.AddTag("key" + index++, 'A');
+
+ activityWithTags.AddTag("key" + index++, new string[] { "val1"
});
+ activityWithTags.AddTag("key" + index++, new byte[] { 123 });
+ activityWithTags.AddTag("key" + index++, new sbyte[] { 123 });
+ activityWithTags.AddTag("key" + index++, new ushort[] { 123 });
+ activityWithTags.AddTag("key" + index++, new short[] { 123 });
+ activityWithTags.AddTag("key" + index++, new uint[] { 123 });
+ activityWithTags.AddTag("key" + index++, new int[] { 123 });
+ activityWithTags.AddTag("key" + index++, new ulong[] { 123 });
+ activityWithTags.AddTag("key" + index++, new long[] { 123 });
+ activityWithTags.AddTag("key" + index++, new float[] { 123 });
+ activityWithTags.AddTag("key" + index++, new double[] { 123 });
+ activityWithTags.AddTag("key" + index++, new decimal[] { 123
});
+ activityWithTags.AddTag("key" + index++, new bool[] { true });
+ activityWithTags.AddTag("key" + index++, new char[] { 'A' });
+
+ activityWithTags.AddTag("key" + index++, new string[] { "val1"
}.AsEnumerable());
+ activityWithTags.AddTag("key" + index++, new byte[] { 123
}.AsEnumerable());
+ activityWithTags.AddTag("key" + index++, new sbyte[] { 123
}.AsEnumerable());
+ activityWithTags.AddTag("key" + index++, new ushort[] { 123
}.AsEnumerable());
+ activityWithTags.AddTag("key" + index++, new short[] { 123
}.AsEnumerable());
+ activityWithTags.AddTag("key" + index++, new uint[] { 123
}.AsEnumerable());
+ activityWithTags.AddTag("key" + index++, new int[] { 123
}.AsEnumerable());
+ activityWithTags.AddTag("key" + index++, new ulong[] { 123
}.AsEnumerable());
+ activityWithTags.AddTag("key" + index++, new long[] { 123
}.AsEnumerable());
+ activityWithTags.AddTag("key" + index++, new float[] { 123
}.AsEnumerable());
+ activityWithTags.AddTag("key" + index++, new double[] { 123
}.AsEnumerable());
+ activityWithTags.AddTag("key" + index++, new decimal[] { 123
}.AsEnumerable());
+ activityWithTags.AddTag("key" + index++, new bool[] { true
}.AsEnumerable());
+ activityWithTags.AddTag("key" + index++, new char[] { 'A'
}.AsEnumerable());
+
+ activityWithTags.AddTag("key" + index++, new
Uri("http://example.com"));
+ Add(activityWithTags);
+ }
+ }
+
+ public SerializableActivityTests(ITestOutputHelper output)
+ {
+ _output = output;
+ }
+
+ [Fact]
+ public async Task
CannnotSerializeAnonymousObjectWithSerializerContext()
+ {
+ Activity activity = new Activity("activity");
+ using (activity.Start())
+ {
+ activity.AddTag("key1", new { Field1 = "value1" });
+ SerializableActivity serializableActivity = new(activity);
+ var stream = new MemoryStream();
+ var serializerOptions = new JsonSerializerOptions
+ {
+ WriteIndented = true,
+ IncludeFields = true,
+ TypeInfoResolver =
SerializableActivitySerializerContext.Default,
+ };
+ await Assert.ThrowsAnyAsync<Exception>(async () => await
JsonSerializer.SerializeAsync(
+ stream,
+ serializableActivity,
+ serializerOptions));
+ }
+ }
+
+ [Theory]
+ [ClassData(typeof(SerializableActivityTestData))]
+ public async Task CanSerializeWithNoDefaultTypeInfoResolver(Activity
activity)
+ {
+ using (activity.Start())
+ {
+ SerializableActivity serializableActivity = new(activity);
+ var stream = new MemoryStream();
+ var serializerOptions = new JsonSerializerOptions
+ {
+ WriteIndented = true,
+ IncludeFields = true,
+ TypeInfoResolver =
SerializableActivitySerializerContext.Default,
+ };
+ await JsonSerializer.SerializeAsync(
+ stream,
+ serializableActivity,
+ serializerOptions);
+ Assert.NotNull(stream);
+ _output.WriteLine("Serialized Activity: {0}",
Encoding.UTF8.GetString(stream.ToArray()));
+ }
+ }
+
+ [Theory]
+ [ClassData(typeof(SerializableActivityTestData))]
+ public async Task CanSerializeWithDefaultTypeInfoResolver(Activity
activity)
+ {
+ using (activity.Start())
+ {
+ SerializableActivity serializableActivity = new(activity);
+ var stream = new MemoryStream();
+ var serializerOptions = new JsonSerializerOptions
+ {
+ WriteIndented = true,
+ IncludeFields = true,
+ TypeInfoResolver = JsonTypeInfoResolver.Combine(
+ SerializableActivitySerializerContext.Default,
+ new DefaultJsonTypeInfoResolver()),
+ };
+ await JsonSerializer.SerializeAsync(
+ stream,
+ serializableActivity,
+ serializerOptions);
+ Assert.NotNull(stream);
+ _output.WriteLine("Serialized Activity: {0}",
Encoding.UTF8.GetString(stream.ToArray()));
+ }
+ }
+
+ [Fact]
+ public async Task
CanSerializeAnonymousObjectWithDefaultTypeInfoResolver()
+ {
+ Activity activity = new Activity("activity");
+ using (activity.Start())
+ {
+ activity.AddTag("key1", new { Field1 = "value1" });
+ SerializableActivity serializableActivity = new(activity);
+ var stream = new MemoryStream();
+ var serializerOptions = new JsonSerializerOptions
+ {
+ WriteIndented = true,
+ IncludeFields = true,
+ TypeInfoResolver = JsonTypeInfoResolver.Combine(
+ SerializableActivitySerializerContext.Default,
+ new DefaultJsonTypeInfoResolver()),
+ };
+ await JsonSerializer.SerializeAsync(
+ stream,
+ serializableActivity,
+ serializerOptions);
+ _output.WriteLine("Serialized Activity: {0}",
Encoding.UTF8.GetString(stream.ToArray()));
+ }
+ }
+ }
+}