From 602cf1603e6be9e961e5512ca770f6765fcb9b91 Mon Sep 17 00:00:00 2001 From: mo khan Date: Sun, 6 Jul 2025 17:29:41 -0600 Subject: feat: add debug logging support for Rust backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implement debug logging in RustConnection that matches Net::HTTP format - Add log_request and log_response methods with detailed HTTP transaction logs - Update Rust client to properly handle request headers - Add comprehensive RDoc documentation for logging methods - Ensure feature parity between Ruby and Rust backends for debugging Debug output format matches Net::HTTP's set_debug_output: -> "GET https://api.example.com/users HTTP/1.1" -> "accept: application/json" -> "" <- "HTTP/1.1 200" <- "content-type: application/json" <- "" <- "{\"users\":[...]}" This maintains the same debugging experience users expect from the Ruby backend while providing the performance benefits of Rust. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- lib/net/hippie/rust_connection.rb | 101 ++++++++++++++++++++++++++++++++++++-- src/lib.rs | 13 ++++- 2 files changed, 109 insertions(+), 5 deletions(-) diff --git a/lib/net/hippie/rust_connection.rb b/lib/net/hippie/rust_connection.rb index 7c37350..fc5d4c6 100644 --- a/lib/net/hippie/rust_connection.rb +++ b/lib/net/hippie/rust_connection.rb @@ -4,13 +4,40 @@ require_relative 'rust_backend' module Net module Hippie - # Rust-powered connection that mimics the Ruby Connection interface + # Rust-powered HTTP connection with debug logging support. + # + # RustConnection provides a high-performance HTTP client using Rust's reqwest library + # while maintaining full compatibility with the Ruby backend interface, including + # comprehensive debug logging that matches Net::HTTP's set_debug_output behavior. + # + # == Debug Logging + # + # When a logger is provided, RustConnection outputs detailed HTTP transaction logs + # in the same format as Net::HTTP: + # + # logger = File.open('http_debug.log', 'w') + # connection = RustConnection.new('https', 'api.example.com', 443, logger: logger) + # + # # Output format: + # # -> "GET https://api.example.com/users HTTP/1.1" + # # -> "accept: application/json" + # # -> "user-agent: net/hippie 2.0.0" + # # -> "" + # # <- "HTTP/1.1 200" + # # <- "content-type: application/json" + # # <- "" + # # <- "{\"users\":[...]}" + # + # @since 2.0.0 + # @see RubyConnection The Ruby/net-http implementation + # @see Connection The backend abstraction layer class RustConnection def initialize(scheme, host, port, options = {}) @scheme = scheme @host = host @port = port @options = options + @logger = options[:logger] # Create the Rust client (simplified version for now) @rust_client = Net::Hippie::RustClient.new @@ -18,13 +45,21 @@ module Net def run(request) url = build_url_for(request.path) - headers = {} # Simplified for now + headers = extract_headers(request) body = request.body || '' method = extract_method(request) + # Debug logging (mimics Net::HTTP's set_debug_output behavior) + log_request(method, url, headers, body) if @logger + begin rust_response = @rust_client.public_send(method.downcase, url, headers, body) - RustBackend::ResponseAdapter.new(rust_response) + response = RustBackend::ResponseAdapter.new(rust_response) + + # Debug log response + log_response(response) if @logger + + response rescue => e # Map Rust errors to Ruby equivalents raise map_rust_error(e) @@ -52,6 +87,66 @@ module Net request.class.name.split('::').last.sub('HTTP', '').downcase end + # Logs HTTP request details in Net::HTTP debug format. + # + # Outputs request line, headers, and body using the same format as + # Net::HTTP's set_debug_output for consistent debugging experience. + # + # @param method [String] HTTP method (GET, POST, etc.) + # @param url [String] Complete request URL + # @param headers [Hash] Request headers + # @param body [String] Request body content + # @since 2.0.0 + def log_request(method, url, headers, body) + # Format similar to Net::HTTP's debug output + @logger << "-> \"#{method.upcase} #{url} HTTP/1.1\"\n" + + # Log headers + headers.each do |key, value| + @logger << "-> \"#{key.downcase}: #{value}\"\n" + end + + @logger << "-> \"\"\n" # Empty line + + # Log body if present + if body && !body.empty? + @logger << "-> \"#{body}\"\n" + end + + @logger.flush if @logger.respond_to?(:flush) + end + + # Logs HTTP response details in Net::HTTP debug format. + # + # Outputs response status, headers, and body (truncated if large) using + # the same format as Net::HTTP's set_debug_output. + # + # @param response [RustBackend::ResponseAdapter] HTTP response object + # @since 2.0.0 + def log_response(response) + # Format similar to Net::HTTP's debug output + @logger << "<- \"HTTP/1.1 #{response.code}\"\n" + + # Log some common response headers if available + %w[content-type content-length location server date].each do |header| + value = response[header] + if value + @logger << "<- \"#{header}: #{value}\"\n" + end + end + + @logger << "<- \"\"\n" # Empty line + + # Log response body (truncated if too long) + body = response.body + if body && !body.empty? + display_body = body.length > 1000 ? "#{body[0...1000]}...[truncated]" : body + @logger << "<- \"#{display_body}\"\n" + end + + @logger.flush if @logger.respond_to?(:flush) + end + def map_rust_error(error) case error.message when /Net::ReadTimeout/ diff --git a/src/lib.rs b/src/lib.rs index 992147c..f99052f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -use magnus::{define_module, function, method, Error, Module, Object, Value, class}; +use magnus::{define_module, function, method, Error, Module, Object, Value, class, RHash, TryConvert}; use magnus::value::ReprValue; use reqwest::{Client, Method, Response}; use std::collections::HashMap; @@ -59,7 +59,7 @@ impl RustClient { &self, method_str: String, url: String, - _headers: Value, // Simplified - ignore headers for now + headers: Value, body: String, ) -> Result { let method = match method_str.to_uppercase().as_str() { @@ -74,6 +74,15 @@ impl RustClient { self.runtime.block_on(async { let mut request_builder = self.client.request(method, &url); + // Add headers if provided + if let Ok(headers_hash) = RHash::from_value(headers) { + for (key, value) in headers_hash { + if let (Ok(key_str), Ok(value_str)) = (String::try_convert(key), String::try_convert(value)) { + request_builder = request_builder.header(&key_str, &value_str); + } + } + } + // Add body if not empty if !body.is_empty() { request_builder = request_builder.body(body); -- cgit v1.2.3