How can I log HTTParty requests and responses for debugging purposes?
Debugging HTTParty requests is essential for troubleshooting API integrations and web scraping applications. HTTParty provides several built-in mechanisms for logging requests and responses, plus you can implement custom logging solutions for more detailed debugging information.
Built-in Debug Option
The simplest way to enable logging in HTTParty is using the built-in debug_output
option:
require 'httparty'
class ApiClient
include HTTParty
# Enable debug output to STDOUT
debug_output $stdout
base_uri 'https://api.example.com'
end
# Make a request with debug output
response = ApiClient.get('/users/1')
This will output detailed information about the request and response:
opening connection to api.example.com:443...
opened
starting SSL for api.example.com:443...
SSL established
<- "GET /users/1 HTTP/1.1\r\nConnection: close\r\nHost: api.example.com\r\n\r\n"
-> "HTTP/1.1 200 OK\r\n"
-> "Content-Type: application/json\r\n"
-> "Content-Length: 156\r\n"
-> "\r\n"
reading 156 bytes...
-> "{\"id\":1,\"name\":\"John Doe\",\"email\":\"john@example.com\"}"
read 156 bytes
connection closed
Logging to a File
You can redirect debug output to a file for persistent logging:
require 'httparty'
class ApiClient
include HTTParty
# Log to a file
debug_output File.open('httparty_debug.log', 'w+')
base_uri 'https://api.example.com'
end
response = ApiClient.get('/users')
Custom Logger Implementation
For more control over logging format and content, implement a custom logger:
require 'httparty'
require 'logger'
class CustomHTTPartyLogger
def initialize(logger)
@logger = logger
end
def <<(data)
@logger.info(data)
end
end
class ApiClient
include HTTParty
logger = Logger.new(STDOUT)
logger.level = Logger::DEBUG
debug_output CustomHTTPartyLogger.new(logger)
base_uri 'https://api.example.com'
end
Advanced Logging with Callbacks
HTTParty supports callbacks that allow you to log specific events during the request lifecycle:
require 'httparty'
require 'logger'
class ApiClient
include HTTParty
base_uri 'https://api.example.com'
# Set up logger
@@logger = Logger.new('api_requests.log')
# Custom parser with logging
def self.perform_request(http_method, path, options, &block)
start_time = Time.now
# Log request details
@@logger.info("Starting #{http_method.upcase} request to #{path}")
@@logger.debug("Request options: #{options.inspect}")
begin
response = super(http_method, path, options, &block)
# Log response details
duration = ((Time.now - start_time) * 1000).round(2)
@@logger.info("Request completed in #{duration}ms with status #{response.code}")
@@logger.debug("Response headers: #{response.headers.inspect}")
@@logger.debug("Response body: #{response.body}")
response
rescue => e
@@logger.error("Request failed: #{e.message}")
raise
end
end
end
# Usage
response = ApiClient.get('/users/1')
Logging Request and Response Bodies Separately
When you need to log only specific parts of the HTTP exchange:
require 'httparty'
require 'json'
class ApiClient
include HTTParty
base_uri 'https://api.example.com'
def self.logged_request(method, path, options = {})
# Log request
puts "=== REQUEST ==="
puts "#{method.upcase} #{path}"
puts "Headers: #{options[:headers]}" if options[:headers]
puts "Body: #{options[:body]}" if options[:body]
puts
# Make request
response = send(method, path, options)
# Log response
puts "=== RESPONSE ==="
puts "Status: #{response.code} #{response.message}"
puts "Headers: #{response.headers}"
puts "Body: #{response.body}"
puts "=" * 50
response
end
end
# Usage
response = ApiClient.logged_request(:get, '/users/1')
Structured Logging with JSON
For applications that require structured logging:
require 'httparty'
require 'json'
require 'logger'
class StructuredLogger
def initialize(logger)
@logger = logger
end
def log_request(method, url, options)
log_entry = {
timestamp: Time.now.iso8601,
type: 'http_request',
method: method.to_s.upcase,
url: url,
headers: options[:headers] || {},
body: options[:body]
}
@logger.info(JSON.generate(log_entry))
end
def log_response(response, duration)
log_entry = {
timestamp: Time.now.iso8601,
type: 'http_response',
status_code: response.code,
duration_ms: duration,
headers: response.headers.to_h,
body_size: response.body&.length || 0
}
@logger.info(JSON.generate(log_entry))
end
end
class ApiClient
include HTTParty
base_uri 'https://api.example.com'
@@structured_logger = StructuredLogger.new(Logger.new('api_structured.log'))
def self.perform_request(http_method, path, options, &block)
start_time = Time.now
full_url = "#{base_uri}#{path}"
@@structured_logger.log_request(http_method, full_url, options)
response = super(http_method, path, options, &block)
duration = ((Time.now - start_time) * 1000).round(2)
@@structured_logger.log_response(response, duration)
response
end
end
Conditional Logging
Enable logging only in specific environments or conditions:
require 'httparty'
class ApiClient
include HTTParty
base_uri 'https://api.example.com'
# Enable debug output only in development/test environments
if ENV['RAILS_ENV'] == 'development' || ENV['DEBUG_HTTPARTY']
debug_output $stdout
end
# Or use a more sophisticated approach
if defined?(Rails) && Rails.env.development?
debug_output Rails.logger
end
end
Logging with Middleware Pattern
Create reusable logging middleware for complex applications:
require 'httparty'
module HTTPartyLoggingMiddleware
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def with_logging(logger = nil)
@logger = logger || Logger.new(STDOUT)
define_singleton_method :perform_request do |http_method, path, options, &block|
request_id = SecureRandom.hex(8)
@logger.info("[#{request_id}] → #{http_method.upcase} #{path}")
@logger.debug("[#{request_id}] Request options: #{options}")
start_time = Time.now
response = super(http_method, path, options, &block)
duration = Time.now - start_time
@logger.info("[#{request_id}] ← #{response.code} (#{(duration * 1000).round}ms)")
@logger.debug("[#{request_id}] Response: #{response.body}")
response
end
self
end
end
end
class ApiClient
include HTTParty
include HTTPartyLoggingMiddleware
base_uri 'https://api.example.com'
end
# Usage
client = ApiClient.with_logging(Logger.new('api.log'))
response = client.get('/users/1')
Integration with Rails Logger
When working within a Rails application, integrate with Rails' logging system:
class ApiClient
include HTTParty
base_uri 'https://api.example.com'
if defined?(Rails)
debug_output Rails.logger
# Custom logging method that respects Rails log levels
def self.rails_logged_request(method, path, options = {})
Rails.logger.debug("HTTParty #{method.upcase} #{base_uri}#{path}")
Rails.logger.debug("Request options: #{options}") if options.any?
response = send(method, path, options)
Rails.logger.info("HTTParty response: #{response.code}")
Rails.logger.debug("Response body: #{response.body}")
response
end
end
end
Performance Considerations
When implementing logging for production environments, consider these performance factors:
class PerformantApiClient
include HTTParty
base_uri 'https://api.example.com'
# Only log response bodies for errors or when explicitly requested
def self.conditional_log_request(method, path, options = {})
log_response_body = options.delete(:log_response_body) || false
response = send(method, path, options)
# Always log basic request info
logger.info("#{method.upcase} #{path} → #{response.code}")
# Log detailed info only for errors or when requested
if response.code >= 400 || log_response_body
logger.error("Error response: #{response.body}")
end
response
end
private
def self.logger
@logger ||= Logger.new('api_errors.log')
end
end
Best Practices
- Security: Never log sensitive data like API keys, passwords, or personal information
- Performance: In production, avoid logging large response bodies unless necessary
- Rotation: Use log rotation to prevent log files from growing too large
- Filtering: Implement filtering to exclude sensitive headers or parameters
- Structured Format: Use structured logging (JSON) for better parsing and analysis
When debugging HTTParty requests becomes complex, consider using specialized tools for monitoring network requests in Puppeteer for more advanced scenarios or implementing custom error handling strategies for robust applications.
By implementing these logging strategies, you'll have comprehensive visibility into your HTTParty requests and responses, making debugging and monitoring much more effective.