Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 28, 2026

This PR enables logging of all JSON-RPC payloads at the Trace level for visibility into wire-level protocol traffic.

Summary

Changes Made

Logger Methods in TransportBase:

  • LogTransportSendingMessageSensitive(string, string) - (Trace level) logs the full JSON payload
  • LogTransportSendingMessageSensitive(JsonRpcMessage) - overload that encapsulates the IsEnabled check and serialization (only this overload needs internal guarding since it does serialization)

Updated Transports (simplified - no external guards needed since arguments are cheap):

  • StreamServerTransport - server-side stream transport
  • StreamClientSessionTransport - client-side stream transport
  • SseClientSessionTransport - client-side SSE transport
  • StreamableHttpClientSessionTransport - client-side HTTP transport

Tests Added:

  • SendMessageAsync_Should_Log_At_Trace_Level
  • ReadMessagesAsync_Should_Log_Received_At_Trace_Level

Usage

To enable JSON-RPC payload logging, configure the logging level to Trace:

builder.Logging.SetMinimumLevel(LogLevel.Trace);

When Trace logging is enabled, you'll see messages like:

trce: Server (stream) transport sending message. Message: '{"jsonrpc":"2.0","method":"tools/list","id":1}'
trce: Server (stream) transport received message. Message: '{"jsonrpc":"2.0","result":{"tools":[]},"id":1}'

The LogTransportSendingMessageSensitive(JsonRpcMessage) overload internally checks IsEnabled(LogLevel.Trace) before serializing to avoid expensive serialization when trace logging is disabled. For call sites passing already-serialized strings, no external guard is needed since the source-generated logger methods handle the check internally.


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

- Add LogTransportSendingMessage and LogTransportSendingMessageSensitive to TransportBase
- Update StreamServerTransport to log sent messages at Trace level
- Update StreamClientSessionTransport to log sent messages at Trace level
- Update SseClientSessionTransport to log sent messages at Trace level
- Update StreamableHttpClientSessionTransport to log sent messages at Trace level
- Add tests for Trace-level logging of sent and received messages

Co-authored-by: stephentoub <[email protected]>
Copilot AI changed the title [WIP] Add logging for JSON RPC payloads at Trace level Add Trace-level logging for JSON-RPC payloads in transports Jan 28, 2026
Copilot AI requested a review from stephentoub January 28, 2026 22:54
…ssages

Addresses review feedback to only have Trace-level logging for JSON-RPC payloads.

Co-authored-by: stephentoub <[email protected]>
@stephentoub
Copy link
Contributor

@copilot it looks like you've only adding tracing for sent payloads... what about received payloads?

Wrap logging calls with Logger.IsEnabled(LogLevel.Trace) to avoid
expensive JSON serialization when trace logging is not enabled.

Co-authored-by: stephentoub <[email protected]>
Guard LogTransportReceivedMessageSensitive calls with IsEnabled(LogLevel.Trace)
checks to avoid unnecessary work when trace logging is disabled.

Co-authored-by: stephentoub <[email protected]>
Copy link
Contributor Author

Copilot AI commented Jan 28, 2026

@copilot it looks like you've only adding tracing for sent payloads... what about received payloads?

The received message logging already existed via LogTransportReceivedMessageSensitive. I've now added IsEnabled(LogLevel.Trace) guards around those calls as well in 669781d to ensure consistency with the sent message logging.

Add an overload that takes a JsonRpcMessage directly, encapsulating
the IsEnabled check and JSON serialization to simplify call sites.

Co-authored-by: stephentoub <[email protected]>
Since the arguments to logging calls are just passing existing variables
(no expensive work), the external guards are redundant - the source-generated
logger methods handle the IsEnabled check internally.

Co-authored-by: stephentoub <[email protected]>
Copilot AI requested a review from stephentoub January 28, 2026 23:22
@stephentoub stephentoub marked this pull request as ready for review January 29, 2026 01:15
@stephentoub stephentoub requested a review from halter73 January 29, 2026 01:15
Copy link
Contributor

@halter73 halter73 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use LoggedTest.LoggerFactory and LoggedTest.MockLoggerProvider in the newly added tests.

@halter73
Copy link
Contributor

We could consider adding similar logging to SseResponseStreamTransport and StreamableHttpServerTransport. It would require adding an optional ILoggerFactory constructor parameter which we probably want to do anyway to avoid swallowing ISseEventStreamWriter errors (#1208), and it might be too noisy on a typical HTTP server to be useful without an explicit opt-in (which we could maybe base on IsDevelopment() in ASP.NET Core apps) beyond trace-level logging. That would definitely be a separate PR though.

To get a similarly verbose logging in Kestrel that includes request and response payloads, you have to explicitly add connection logging middleware. I was going to suggest maybe adding message filters (#1207) to the client in addition to the server to cover this scenario, but given that clients will typically have a specific ILoggerFactory explicitly passed in as a parameter rather than provided ambiently by DI, I think it's fine for trace level logging to be this verbose. It appears it already was for incoming messages.

@halter73
Copy link
Contributor

halter73 commented Jan 29, 2026

@copilot Can you use the LoggerFactory property from the base class and get rid of the local mockLoggerProvider variable? By using the LoggerFactory property from LoggedTest, the MockLoggerProvider should be wired up automatically, so you shouldn't need the traceLoggerFactory either. Please update both of the new tests.

…tests

Simplified the tests by using the base class's LoggerFactory and MockLoggerProvider
instead of creating local instances. The LoggerFactory is overridden in the constructor
to use LogLevel.Trace for the trace logging tests.

Co-authored-by: halter73 <[email protected]>
Copy link
Contributor Author

Copilot AI commented Jan 29, 2026

@copilot Can you use the LoggerFactory property from the base class and get rid of the local mockLoggerProvider variable? By using the LoggerFactory property from LoggedTest, the `MockLoggerPr...

Updated the tests in 843ccca to use LoggerFactory and MockLoggerProvider from the base class. The LoggerFactory is overridden in the constructor to set LogLevel.Trace as the minimum level.

@halter73 halter73 merged commit ee8c485 into main Jan 29, 2026
7 of 10 checks passed
@halter73 halter73 deleted the copilot/log-json-rpc-payloads branch January 29, 2026 19:26
Copilot AI added a commit that referenced this pull request Jan 29, 2026
…g level

Address feedback from PR #1191: The class-level LoggerFactory override was removed
from the constructor (now uses base class default of Debug). The trace-level
logging tests now create their own local MockLoggerProvider and trace-level
LoggerFactory to properly test Trace-level logging without affecting other tests.

Co-authored-by: stephentoub <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants