.Net: fix: fall back to ToString() when logging function results with unregistered types#13884
Conversation
…n result logging (fixes microsoft#13681) When a KernelFunction returns a type not registered in AbstractionsJsonContext (e.g. Microsoft.Extensions.AI.TextContent from MCP tools), and JSON serialization is attempted with AOT-safe JsonSerializerOptions, a NotSupportedException is thrown and swallowed, causing the log message to show "Failed to log function result value" instead of useful content. This change adds a ToString() fallback in the NotSupportedException catch block so that result values still produce meaningful log output even when their runtime type is absent from the source-generated JSON context.
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 92%
✓ Correctness
The change adds a reasonable ToString() fallback when JSON serialization throws NotSupportedException for unregistered types (e.g., in AOT scenarios). The fallback chain is correct: try GetValue() → try JSON serialization → try ToString() → log error. The inner bare catch is consistent with the existing pattern at line 143. The test properly simulates the scenario with a source-generated restricted JsonSerializerContext. No correctness issues found.
✓ Security Reliability
This change adds a ToString() fallback in the NotSupportedException catch block of LogFunctionResultValueInternal, invoked when JSON serialization fails for unregistered types (e.g., in AOT scenarios). The change is well-bounded and safe: (1) the logging path is gated behind LogLevel.Trace which is disabled by default (line 108), (2) the ToString() output is passed as a structured log parameter via LogerMessage.Define, not string-interpolated, so there is no log injection risk, (3) null safety is properly handled via null-conditional operators and ?? string.Empty, (4) the bare inner catch block is consistent with the existing pattern on line 143 and the method already suppresses CA1031 (line 130), and (5) the outer NotSupportedException ex variable is correctly preserved and logged if the ToString() fallback itself fails. The test coverage validates the expected behavior. No security or reliability issues found.
✓ Test Coverage
The PR adds a ToString() fallback when JSON serialization throws NotSupportedException during function result logging. The new test covers the primary happy-path scenario well — it verifies the ToString() output is logged instead of the error message. However, the diff introduces a nested try/catch where the inner catch block (handling the case when ToString() itself throws) has no corresponding test. This is a minor coverage gap for a defensive code path.
✓ Design Approach
The change adds a
ToString()fallback inLogFunctionResultValueInternalwhen JSON serialization throwsNotSupportedException(e.g., in AOT scenarios where unregistered MEAITypes likeTextContentare returned). The approach is sound for a trace-level logging path: JSON is still tried first (more informative for complex types), and only when that fails doesToString()serve as a last resort. The bare innercatchis consistent with the existing pattern on line 143. One design inconsistency worth noting: the symmetricLogFunctionArgumentsInternal(lines 65–90) has the identicalNotSupportedExceptioncatch but still logs the opaque error string rather than falling back toToString(). If the fallback is the right approach here, the arguments path has the same gap.
Suggestions
- Add a test for the inner catch block (lines 170-173) where ToString() itself throws, verifying that the original 'Failed to log function result value' message with the NotSupportedException is still logged. For example, create a type whose ToString() throws and assert the error message and exception are passed to the logger.
- The parallel LogFunctionArgumentsInternal method (lines 65–90) has the same NotSupportedException catch pattern but does not fall back to ToString(). If arguments can also contain unregistered types, that path will still log the unhelpful 'Failed to serialize arguments to Json' message. Consider applying the same fallback for consistency.
Automated review by octo-patch's agents
Fixes #13681
Problem
When a
KernelFunctionreturns a value whose runtime type is not registered inAbstractionsJsonContext(e.g.Microsoft.Extensions.AI.TextContentreturned by an MCP tool via.AsKernelFunction()), the function result logging in AOT/source-generation mode throwsNotSupportedExceptionduring JSON serialization. The exception is caught but produces an unhelpful log entry:Solution
Add a
ToString()fallback in theNotSupportedExceptioncatch block ofLogFunctionResultValueInternal. When JSON serialization fails because the runtime type is absent from the source-generated context, the logger now callsresultValue?.Value?.ToString()to produce useful output instead of the generic error message.Testing
ItShouldFallBackToToStringWhenJsonSerializationIsNotSupportedunit test that uses a restricted source-generatedJsonSerializerContext(containing onlyobjectandIDictionary<string, object?>) to simulate the AOT scenario, verifying thatToString()output appears in the log instead of the failure message.ItShouldLogFunctionResultOfAnyTypetest cases are unchanged.