Class: A2A::Client::Middleware::LoggingInterceptor
- Inherits:
-
Object
- Object
- A2A::Client::Middleware::LoggingInterceptor
- Defined in:
- lib/a2a/client/middleware/logging_interceptor.rb
Instance Attribute Summary collapse
-
#log_errors ⇒ Object
readonly
Returns the value of attribute log_errors.
-
#log_level ⇒ Object
readonly
Returns the value of attribute log_level.
-
#log_requests ⇒ Object
readonly
Returns the value of attribute log_requests.
-
#log_responses ⇒ Object
readonly
Returns the value of attribute log_responses.
-
#logger ⇒ Object
readonly
Returns the value of attribute logger.
Instance Method Summary collapse
-
#call(request, context, next_middleware) ⇒ Object
Execute request with logging.
-
#create_default_logger ⇒ Logger
private
Create default logger.
-
#extract_body(request) ⇒ String, ...
private
Extract body from request.
-
#extract_headers(request) ⇒ Hash
private
Extract headers from request.
-
#extract_method(request) ⇒ String?
private
Extract method from request.
-
#extract_response_body(response) ⇒ String, ...
private
Extract body from response.
-
#extract_response_headers(response) ⇒ Hash
private
Extract headers from response.
-
#extract_status(response) ⇒ Integer, ...
private
Extract status from response.
-
#extract_url(request) ⇒ String?
private
Extract URL from request.
-
#format_log_message(title, data) ⇒ String
private
Format log message.
-
#generate_request_id ⇒ String
private
Generate a unique request ID.
-
#initialize(logger: nil, log_level: :info, log_requests: true, log_responses: true, log_errors: true, mask_sensitive: true) ⇒ LoggingInterceptor
constructor
Initialize logging interceptor.
-
#log_error(error, context, duration) ⇒ Object
Log an error.
-
#log_request(request, context) ⇒ Object
Log a request.
-
#log_response(response, context, duration) ⇒ Object
Log a response.
-
#mask_body(body) ⇒ Object
private
Mask sensitive body content.
-
#mask_headers(headers) ⇒ Hash
private
Mask sensitive headers.
-
#mask_value(value) ⇒ String
private
Mask a sensitive value.
-
#parse_json_body(body) ⇒ Hash, String
private
Parse JSON body.
-
#response_successful?(response) ⇒ Boolean
private
Check if response was successful.
-
#sanitize_context(context) ⇒ Hash
private
Sanitize context for logging.
-
#validate_configuration! ⇒ Object
private
Validate configuration.
Constructor Details
#initialize(logger: nil, log_level: :info, log_requests: true, log_responses: true, log_errors: true, mask_sensitive: true) ⇒ LoggingInterceptor
Initialize logging interceptor
27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 27 def initialize(logger: nil, log_level: :info, log_requests: true, log_responses: true, log_errors: true, mask_sensitive: true) @logger = logger || create_default_logger @log_level = log_level @log_requests = log_requests @log_responses = log_responses @log_errors = log_errors @mask_sensitive = mask_sensitive validate_configuration! end |
Instance Attribute Details
#log_errors ⇒ Object (readonly)
Returns the value of attribute log_errors.
16 17 18 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 16 def log_errors @log_errors end |
#log_level ⇒ Object (readonly)
Returns the value of attribute log_level.
16 17 18 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 16 def log_level @log_level end |
#log_requests ⇒ Object (readonly)
Returns the value of attribute log_requests.
16 17 18 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 16 def log_requests @log_requests end |
#log_responses ⇒ Object (readonly)
Returns the value of attribute log_responses.
16 17 18 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 16 def log_responses @log_responses end |
#logger ⇒ Object (readonly)
Returns the value of attribute logger.
16 17 18 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 16 def logger @logger end |
Instance Method Details
#call(request, context, next_middleware) ⇒ Object
Execute request with logging
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 46 def call(request, context, next_middleware) request_id = context[:request_id] || generate_request_id context[:request_id] = request_id start_time = Time.now log_request(request, context) if @log_requests begin response = next_middleware.call(request, context) duration = Time.now - start_time log_response(response, context, duration) if @log_responses response rescue StandardError => e duration = Time.now - start_time log_error(e, context, duration) if @log_errors raise e end end |
#create_default_logger ⇒ Logger (private)
Create default logger
138 139 140 141 142 143 144 145 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 138 def create_default_logger logger = Logger.new($stdout) logger.level = Logger::INFO logger.formatter = proc do |severity, datetime, _progname, msg| "[#{datetime.strftime('%Y-%m-%d %H:%M:%S')}] #{severity}: #{msg}\n" end logger end |
#extract_body(request) ⇒ String, ... (private)
Extract body from request
212 213 214 215 216 217 218 219 220 221 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 212 def extract_body(request) if request.respond_to?(:body) body = request.body return parse_json_body(body) if body.is_a?(String) body elsif request.respond_to?(:to_h) request.to_h end end |
#extract_headers(request) ⇒ Hash (private)
Extract headers from request
199 200 201 202 203 204 205 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 199 def extract_headers(request) if request.respond_to?(:headers) request.headers.to_h else {} end end |
#extract_method(request) ⇒ String? (private)
Extract method from request
173 174 175 176 177 178 179 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 173 def extract_method(request) if request.respond_to?(:method) request.method elsif request.respond_to?(:[]) request["method"] || request[:method] end end |
#extract_response_body(response) ⇒ String, ... (private)
Extract body from response
254 255 256 257 258 259 260 261 262 263 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 254 def extract_response_body(response) if response.respond_to?(:body) body = response.body return parse_json_body(body) if body.is_a?(String) body elsif response.respond_to?(:to_h) response.to_h end end |
#extract_response_headers(response) ⇒ Hash (private)
Extract headers from response
241 242 243 244 245 246 247 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 241 def extract_response_headers(response) if response.respond_to?(:headers) response.headers.to_h else {} end end |
#extract_status(response) ⇒ Integer, ... (private)
Extract status from response
228 229 230 231 232 233 234 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 228 def extract_status(response) if response.respond_to?(:status) response.status elsif response.respond_to?(:[]) response["status"] || response[:status] end end |
#extract_url(request) ⇒ String? (private)
Extract URL from request
186 187 188 189 190 191 192 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 186 def extract_url(request) if request.respond_to?(:url) request.url elsif request.respond_to?(:uri) request.uri.to_s end end |
#format_log_message(title, data) ⇒ String (private)
Format log message
162 163 164 165 166 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 162 def (title, data) "#{title}: #{JSON.pretty_generate(data)}" rescue JSON::GeneratorError "#{title}: #{data.inspect}" end |
#generate_request_id ⇒ String (private)
Generate a unique request ID
151 152 153 154 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 151 def generate_request_id require "securerandom" SecureRandom.hex(8) end |
#log_error(error, context, duration) ⇒ Object
Log an error
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 116 def log_error(error, context, duration) log_data = { type: "error", request_id: context[:request_id], timestamp: Time.now.utc.iso8601, duration_ms: (duration * 1000).round(2), error_class: error.class.name, error_message: error., error_code: error.respond_to?(:code) ? error.code : nil, backtrace: error.backtrace&.first(10), retry_attempt: context[:retry_attempt] } @logger.error(("A2A Error", log_data)) end |
#log_request(request, context) ⇒ Object
Log a request
73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 73 def log_request(request, context) log_data = { type: "request", request_id: context[:request_id], timestamp: Time.now.utc.iso8601, method: extract_method(request), url: extract_url(request), headers: mask_headers(extract_headers(request)), body: mask_body(extract_body(request)), context: sanitize_context(context) } @logger.send(@log_level, ("A2A Request", log_data)) end |
#log_response(response, context, duration) ⇒ Object
Log a response
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 94 def log_response(response, context, duration) log_data = { type: "response", request_id: context[:request_id], timestamp: Time.now.utc.iso8601, duration_ms: (duration * 1000).round(2), status: extract_status(response), headers: mask_headers(extract_response_headers(response)), body: mask_body(extract_response_body(response)), success: response_successful?(response) } level = response_successful?(response) ? @log_level : :warn @logger.send(level, ("A2A Response", log_data)) end |
#mask_body(body) ⇒ Object (private)
Mask sensitive body content
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 320 def mask_body(body) return body unless @mask_sensitive return body unless body.is_a?(Hash) masked = body.dup # Mask common sensitive fields %w[password secret token key credential].each do |field| masked.each do |k, v| masked[k] = mask_value(v) if k.to_s.downcase.include?(field) end end masked end |
#mask_headers(headers) ⇒ Hash (private)
Mask sensitive headers
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 298 def mask_headers(headers) return headers unless @mask_sensitive masked = headers.dup # Mask authorization headers masked.each do |key, value| next unless key.to_s.downcase.include?("authorization") || key.to_s.downcase.include?("token") || key.to_s.downcase.include?("key") masked[key] = mask_value(value) end masked end |
#mask_value(value) ⇒ String (private)
Mask a sensitive value
341 342 343 344 345 346 347 348 349 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 341 def mask_value(value) return "[nil]" if value.nil? return "[empty]" if value.to_s.empty? str = value.to_s return str if str.length <= 8 "#{str[0..3]}#{'*' * (str.length - 8)}#{str[-4..]}" end |
#parse_json_body(body) ⇒ Hash, String (private)
Parse JSON body
287 288 289 290 291 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 287 def parse_json_body(body) JSON.parse(body) rescue JSON::ParserError body end |
#response_successful?(response) ⇒ Boolean (private)
Check if response was successful
270 271 272 273 274 275 276 277 278 279 280 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 270 def response_successful?(response) if response.respond_to?(:success?) response.success? elsif response.respond_to?(:status) (200..299).cover?(response.status) elsif response.respond_to?(:[]) !response["error"] && !response[:error] else true # Assume success if we can't determine end end |
#sanitize_context(context) ⇒ Hash (private)
Sanitize context for logging
356 357 358 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 356 def sanitize_context(context) context.reject { |k, _v| k.to_s.include?("password") || k.to_s.include?("secret") } end |
#validate_configuration! ⇒ Object (private)
Validate configuration
362 363 364 365 366 367 |
# File 'lib/a2a/client/middleware/logging_interceptor.rb', line 362 def validate_configuration! valid_levels = i[debug info warn error] return if valid_levels.include?(@log_level) raise ArgumentError, "Invalid log level: #{@log_level}. Must be one of: #{valid_levels.join(', ')}" end |