diff --git a/kernel/kernel-api/src/main/java/io/delta/kernel/internal/metrics/MetricsReportSerializers.java b/kernel/kernel-api/src/main/java/io/delta/kernel/internal/metrics/MetricsReportSerializer.java similarity index 71% rename from kernel/kernel-api/src/main/java/io/delta/kernel/internal/metrics/MetricsReportSerializers.java rename to kernel/kernel-api/src/main/java/io/delta/kernel/internal/metrics/MetricsReportSerializer.java index 12f6ac9f88b..1568a34b7f0 100644 --- a/kernel/kernel-api/src/main/java/io/delta/kernel/internal/metrics/MetricsReportSerializers.java +++ b/kernel/kernel-api/src/main/java/io/delta/kernel/internal/metrics/MetricsReportSerializer.java @@ -26,46 +26,34 @@ import io.delta.kernel.expressions.Column; import io.delta.kernel.expressions.Predicate; import io.delta.kernel.metrics.MetricsReport; -import io.delta.kernel.metrics.ScanReport; -import io.delta.kernel.metrics.SnapshotReport; -import io.delta.kernel.metrics.TransactionReport; import io.delta.kernel.types.StructType; import java.io.IOException; /** Defines JSON serializers for {@link MetricsReport} types */ -public final class MetricsReportSerializers { +public final class MetricsReportSerializer { ///////////////// // Public APIs // ///////////////// /** - * Serializes a {@link SnapshotReport} to a JSON string + * Serializes any {@link MetricsReport} to a JSON string using the configured ObjectMapper. This + * method works for any report type that has proper Jackson annotations. * - * @throws JsonProcessingException - */ - public static String serializeSnapshotReport(SnapshotReport snapshotReport) - throws JsonProcessingException { - return OBJECT_MAPPER.writeValueAsString(snapshotReport); - } - - /** - * Serializes a {@link ScanReport} to a JSON string + *

For successful serialization, the report class should have: * - * @throws JsonProcessingException - */ - public static String serializeScanReport(ScanReport scanReport) throws JsonProcessingException { - return OBJECT_MAPPER.writeValueAsString(scanReport); - } - - /** - * Serializes a {@link TransactionReport} to a JSON string + *

* - * @throws JsonProcessingException + * @throws JsonProcessingException if the report cannot be serialized (e.g., circular references, + * unsupported types, or inaccessible fields) */ - public static String serializeTransactionReport(TransactionReport transactionReport) - throws JsonProcessingException { - return OBJECT_MAPPER.writeValueAsString(transactionReport); + public static String serializeMetricsReport(MetricsReport report) throws JsonProcessingException { + return OBJECT_MAPPER.writeValueAsString(report); } ///////////////////////////////// @@ -96,5 +84,5 @@ public void serialize(Column value, JsonGenerator gen, SerializerProvider serial } } - private MetricsReportSerializers() {} + private MetricsReportSerializer() {} } diff --git a/kernel/kernel-api/src/test/scala/io/delta/kernel/internal/metrics/MetricsReportSerializerSuite.scala b/kernel/kernel-api/src/test/scala/io/delta/kernel/internal/metrics/MetricsReportSerializerSuite.scala index 9b65bed67b8..db1dbf083c8 100644 --- a/kernel/kernel-api/src/test/scala/io/delta/kernel/internal/metrics/MetricsReportSerializerSuite.scala +++ b/kernel/kernel-api/src/test/scala/io/delta/kernel/internal/metrics/MetricsReportSerializerSuite.scala @@ -69,7 +69,7 @@ class MetricsReportSerializerSuite extends AnyFunSuite { |} |} |""".stripMargin.replaceAll("\n", "") - assert(expectedJson == MetricsReportSerializers.serializeSnapshotReport(snapshotReport)) + assert(expectedJson == MetricsReportSerializer.serializeMetricsReport(snapshotReport)) } test("SnapshotReport serializer") { @@ -106,7 +106,7 @@ class MetricsReportSerializerSuite extends AnyFunSuite { |} |} |""".stripMargin.replaceAll("\n", "") - assert(expectedJson == MetricsReportSerializers.serializeSnapshotReport(snapshotReport1)) + assert(expectedJson == MetricsReportSerializer.serializeMetricsReport(snapshotReport1)) // Check with test function testSnapshotReport(snapshotReport1) @@ -152,7 +152,7 @@ class MetricsReportSerializerSuite extends AnyFunSuite { |} |} |""".stripMargin.replaceAll("\n", "") - assert(expectedJson == MetricsReportSerializers.serializeTransactionReport(transactionReport)) + assert(expectedJson == MetricsReportSerializer.serializeMetricsReport(transactionReport)) } test("TransactionReport serializer") { @@ -204,7 +204,7 @@ class MetricsReportSerializerSuite extends AnyFunSuite { |} |} |""".stripMargin.replaceAll("\n", "") - assert(expectedJson == MetricsReportSerializers.serializeTransactionReport(transactionReport1)) + assert(expectedJson == MetricsReportSerializer.serializeMetricsReport(transactionReport1)) // Check with test function testTransactionReport(transactionReport1) @@ -257,7 +257,7 @@ class MetricsReportSerializerSuite extends AnyFunSuite { |} |} |""".stripMargin.replaceAll("\n", "") - assert(expectedJson == MetricsReportSerializers.serializeScanReport(scanReport)) + assert(expectedJson == MetricsReportSerializer.serializeMetricsReport(scanReport)) } test("ScanReport serializer") { @@ -319,7 +319,7 @@ class MetricsReportSerializerSuite extends AnyFunSuite { |} |} |""".stripMargin.replaceAll("\n", "") - assert(expectedJson == MetricsReportSerializers.serializeScanReport(scanReport1)) + assert(expectedJson == MetricsReportSerializer.serializeMetricsReport(scanReport1)) // Check with test function testScanReport(scanReport1) @@ -339,4 +339,28 @@ class MetricsReportSerializerSuite extends AnyFunSuite { Optional.empty()) testScanReport(scanReport2) } + + test("Generic serializer handles custom/new report types") { + // This test demonstrates that any custom MetricsReport implementation with proper + // Jackson annotations can be serialized without modifying MetricsReportSerializer + + val fooReport = new FooReport("bar-value", 42, "zap-value") + + val actualJson = MetricsReportSerializer.serializeMetricsReport(fooReport) + + val expectedJson = """{"bar":"bar-value","zip":42,"zap":"zap-value"}""" + + assert(expectedJson == actualJson) + } } + +/** + * Example custom report type demonstrating extensibility. + * Uses public fields (via @BeanProperty) to work with Jackson, like UcCommitTelemetry.Report. + */ +@com.fasterxml.jackson.annotation.JsonPropertyOrder(Array("bar", "zip", "zap")) +class FooReport( + @scala.beans.BeanProperty val bar: String, + @scala.beans.BeanProperty val zip: Int, + @scala.beans.BeanProperty val zap: String) + extends io.delta.kernel.metrics.MetricsReport diff --git a/kernel/kernel-defaults/src/main/java/io/delta/kernel/defaults/engine/LoggingMetricsReporter.java b/kernel/kernel-defaults/src/main/java/io/delta/kernel/defaults/engine/LoggingMetricsReporter.java index a10f995ee14..6e202fd664c 100644 --- a/kernel/kernel-defaults/src/main/java/io/delta/kernel/defaults/engine/LoggingMetricsReporter.java +++ b/kernel/kernel-defaults/src/main/java/io/delta/kernel/defaults/engine/LoggingMetricsReporter.java @@ -17,11 +17,8 @@ import com.fasterxml.jackson.core.JsonProcessingException; import io.delta.kernel.engine.MetricsReporter; -import io.delta.kernel.internal.metrics.MetricsReportSerializers; +import io.delta.kernel.internal.metrics.MetricsReportSerializer; import io.delta.kernel.metrics.MetricsReport; -import io.delta.kernel.metrics.ScanReport; -import io.delta.kernel.metrics.SnapshotReport; -import io.delta.kernel.metrics.TransactionReport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,23 +33,8 @@ public class LoggingMetricsReporter implements MetricsReporter { @Override public void report(MetricsReport report) { try { - if (report instanceof SnapshotReport) { - logger.info( - "SnapshotReport = {}", - MetricsReportSerializers.serializeSnapshotReport((SnapshotReport) report)); - } else if (report instanceof ScanReport) { - logger.info( - "ScanReport = {}", MetricsReportSerializers.serializeScanReport((ScanReport) report)); - } else if (report instanceof TransactionReport) { - logger.info( - "TransactionReport = {}", - MetricsReportSerializers.serializeTransactionReport((TransactionReport) report)); - } else { - logger.info( - "{} = [{} does not support serializing this type of MetricReport]", - report.getClass(), - this.getClass()); - } + final String reportType = report.getClass().getSimpleName(); + logger.info("{} = {}", reportType, MetricsReportSerializer.serializeMetricsReport(report)); } catch (JsonProcessingException e) { logger.info("Encountered exception while serializing report {}: {}", report, e); }