Class: A2A::Server::Middleware::LoggingMiddleware

Inherits:
Object
  • Object
show all
Includes:
Utils::RailsDetection
Defined in:
lib/a2a/server/middleware/logging_middleware.rb

Constant Summary collapse

FORMATS =

Logging formats

%i[simple detailed structured json].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Utils::RailsDetection

#rails_application, #rails_available?, #rails_development?, #rails_environment, #rails_logger, #rails_production?, #rails_version, #rails_version_requires_validation?, #rails_version_supported?

Constructor Details

#initialize(logger: nil, level: :info, format: :detailed, **options) ⇒ LoggingMiddleware

Initialize logging middleware

Parameters:

  • (defaults to: nil)

    The logger instance to use

  • (defaults to: :info)

    The log level (:debug, :info, :warn, :error)

  • (defaults to: :detailed)

    The log format (:simple, :detailed, :structured, :json)

  • Additional logging options

Options Hash (**options):

  • :log_params (Boolean)

    Whether to log request parameters

  • :log_response (Boolean)

    Whether to log response data

  • :log_errors (Boolean)

    Whether to log error details

  • :filtered_params (Array<String>)

    Parameters to filter from logs



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/a2a/server/middleware/logging_middleware.rb', line 44

def initialize(logger: nil, level: :info, format: :detailed, **options)
  @logger = logger || default_logger
  @level = level
  @format = format
  @options = {
    log_params: true,
    log_response: false,
    log_errors: true,
    filtered_params: %w[password token api_key secret],
    include_context: true,
    include_timing: true
  }.merge(options)

  validate_format!
end

Instance Attribute Details

#formatObject (readonly)

Returns the value of attribute format.



28
29
30
# File 'lib/a2a/server/middleware/logging_middleware.rb', line 28

def format
  @format
end

#levelObject (readonly)

Returns the value of attribute level.



28
29
30
# File 'lib/a2a/server/middleware/logging_middleware.rb', line 28

def level
  @level
end

#loggerObject

Returns the value of attribute logger.



27
28
29
# File 'lib/a2a/server/middleware/logging_middleware.rb', line 27

def logger
  @logger
end

#optionsObject (readonly)

Returns the value of attribute options.



28
29
30
# File 'lib/a2a/server/middleware/logging_middleware.rb', line 28

def options
  @options
end

Instance Method Details

#add_filtered_param(param) ⇒ Object

Add filtered parameter

Parameters:

  • Parameter name to filter



102
103
104
# File 'lib/a2a/server/middleware/logging_middleware.rb', line 102

def add_filtered_param(param)
  @options[:filtered_params] << param.to_s
end

#call(request, context) { ... } ⇒ Object

Process logging for a request

Parameters:

  • The JSON-RPC request

  • The request context

Yields:

  • Block to continue the middleware chain

Returns:

  • The result from the next middleware or handler



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/a2a/server/middleware/logging_middleware.rb', line 67

def call(request, context)
  start_time = Time.now
  request_id = generate_request_id(request, context)

  # Log request start
  log_request_start(request, context, request_id)

  begin
    # Execute next middleware/handler
    result = yield

    # Log successful completion
    duration = Time.now - start_time
    log_request_success(request, context, result, duration, request_id)

    result
  rescue StandardError => e
    # Log error
    duration = Time.now - start_time
    log_request_error(request, context, e, duration, request_id)

    # Re-raise the error
    raise
  end
end

#default_loggerObject (private)

Get default logger



126
127
128
129
130
131
132
133
134
# File 'lib/a2a/server/middleware/logging_middleware.rb', line 126

def default_logger
  if rails_logger
    rails_logger
  else
    Logger.new($stdout).tap do |logger|
      logger.level = Logger::INFO
    end
  end
end

#filter_sensitive_data(data) ⇒ Object (private)

Filter sensitive data from parameters/responses

Parameters:

  • The data to filter

Returns:

  • Filtered data



341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/a2a/server/middleware/logging_middleware.rb', line 341

def filter_sensitive_data(data)
  case data
  when Hash
    filtered = {}
    data.each do |key, value|
      filtered[key] = if @options[:filtered_params].include?(key.to_s.downcase)
                        "[FILTERED]"
                      else
                        filter_sensitive_data(value)
                      end
    end
    filtered
  when Array
    data.map { |item| filter_sensitive_data(item) }
  else
    data
  end
end

#format_structured_log(data) ⇒ String (private)

Format structured log data

Parameters:

  • The log data

Returns:

  • Formatted log message



327
328
329
330
331
332
333
334
# File 'lib/a2a/server/middleware/logging_middleware.rb', line 327

def format_structured_log(data)
  if @format == :json
    JSON.generate(data)
  else
    # Key=value format for structured logs
    data.map { |k, v| "#{k}=#{v.inspect}" }.join(" ")
  end
end

#generate_request_id(request, _context) ⇒ String (private)

Generate a unique request ID

Parameters:

  • The request

  • The context

Returns:

  • The request ID



142
143
144
145
146
147
148
# File 'lib/a2a/server/middleware/logging_middleware.rb', line 142

def generate_request_id(request, _context)
  # Use existing request ID if available
  return request.id.to_s if request.id

  # Generate a new ID
  "req_#{Time.now.to_f}_#{rand(10_000)}"
end

#log_detailed_error(request, _context, error, duration, request_id) ⇒ Object (private)

Log detailed request error



241
242
243
244
245
246
247
248
249
250
# File 'lib/a2a/server/middleware/logging_middleware.rb', line 241

def log_detailed_error(request, _context, error, duration, request_id)
  message = "A2A Request Failed - Method: #{request.method}, ID: #{request_id}, Duration: #{duration.round(3)}s"
  message += ", Error: #{error.class.name}: #{error.message}"

  if @options[:log_errors] && error.respond_to?(:backtrace) && error.backtrace
    message += ", Backtrace: #{error.backtrace.first(3).join(' | ')}"
  end

  @logger.error(message)
end

#log_detailed_start(request, context, request_id) ⇒ Object (private)

Log detailed request start



213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/a2a/server/middleware/logging_middleware.rb', line 213

def log_detailed_start(request, context, request_id)
  message = "A2A Request Started - Method: #{request.method}, ID: #{request_id}"

  if @options[:log_params] && !request.params.empty?
    filtered_params = filter_sensitive_data(request.params)
    message += ", Params: #{filtered_params.inspect}"
  end

  message += ", Authenticated: true" if @options[:include_context] && context.authenticated?

  @logger.send(@level, message)
end

#log_detailed_success(request, _context, result, duration, request_id) ⇒ Object (private)

Log detailed request success



228
229
230
231
232
233
234
235
236
237
# File 'lib/a2a/server/middleware/logging_middleware.rb', line 228

def log_detailed_success(request, _context, result, duration, request_id)
  message = "A2A Request Completed - Method: #{request.method}, ID: #{request_id}, Duration: #{duration.round(3)}s"

  if @options[:log_response] && result
    filtered_result = filter_sensitive_data(result)
    message += ", Response: #{filtered_result.inspect}"
  end

  @logger.send(@level, message)
end

#log_json_error(request, context, error, duration, request_id) ⇒ Object (private)



318
319
320
# File 'lib/a2a/server/middleware/logging_middleware.rb', line 318

def log_json_error(request, context, error, duration, request_id)
  log_structured_error(request, context, error, duration, request_id)
end

#log_json_start(request, context, request_id) ⇒ Object (private)

Log JSON format (same as structured but always JSON)



310
311
312
# File 'lib/a2a/server/middleware/logging_middleware.rb', line 310

def log_json_start(request, context, request_id)
  log_structured_start(request, context, request_id)
end

#log_json_success(request, context, result, duration, request_id) ⇒ Object (private)



314
315
316
# File 'lib/a2a/server/middleware/logging_middleware.rb', line 314

def log_json_success(request, context, result, duration, request_id)
  log_structured_success(request, context, result, duration, request_id)
end

#log_request_error(request, context, error, duration, request_id) ⇒ Object (private)

Log request error

Parameters:

  • The request

  • The context

  • The error

  • Request duration in seconds

  • The request ID



198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/a2a/server/middleware/logging_middleware.rb', line 198

def log_request_error(request, context, error, duration, request_id)
  case @format
  when :simple
    @logger.error("A2A Error: #{request.method} - #{error.class.name}: #{error.message}")
  when :detailed
    log_detailed_error(request, context, error, duration, request_id)
  when :structured
    log_structured_error(request, context, error, duration, request_id)
  when :json
    log_json_error(request, context, error, duration, request_id)
  end
end

#log_request_start(request, context, request_id) ⇒ Object (private)

Log request start

Parameters:

  • The request

  • The context

  • The request ID



156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/a2a/server/middleware/logging_middleware.rb', line 156

def log_request_start(request, context, request_id)
  case @format
  when :simple
    @logger.send(@level, "A2A Request: #{request.method}")
  when :detailed
    log_detailed_start(request, context, request_id)
  when :structured
    log_structured_start(request, context, request_id)
  when :json
    log_json_start(request, context, request_id)
  end
end

#log_request_success(request, context, result, duration, request_id) ⇒ Object (private)

Log successful request completion

Parameters:

  • The request

  • The context

  • The response result

  • Request duration in seconds

  • The request ID



177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/a2a/server/middleware/logging_middleware.rb', line 177

def log_request_success(request, context, result, duration, request_id)
  case @format
  when :simple
    @logger.send(@level, "A2A Response: #{request.method} (#{duration.round(3)}s)")
  when :detailed
    log_detailed_success(request, context, result, duration, request_id)
  when :structured
    log_structured_success(request, context, result, duration, request_id)
  when :json
    log_json_success(request, context, result, duration, request_id)
  end
end

#log_structured_error(request, _context, error, duration, request_id) ⇒ Object (private)

Log structured request error



290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/a2a/server/middleware/logging_middleware.rb', line 290

def log_structured_error(request, _context, error, duration, request_id)
  data = {
    event: "a2a_request_error",
    method: request.method,
    request_id: request_id,
    duration: duration.round(3),
    error_class: error.class.name,
    error_message: error.message,
    timestamp: Time.now.iso8601
  }

  data[:backtrace] = error.backtrace.first(5) if @options[:log_errors] && error.respond_to?(:backtrace) && error.backtrace

  data[:error_code] = error.code if error.respond_to?(:code)

  @logger.error(format_structured_log(data))
end

#log_structured_start(request, context, request_id) ⇒ Object (private)

Log structured request start



254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/a2a/server/middleware/logging_middleware.rb', line 254

def log_structured_start(request, context, request_id)
  data = {
    event: "a2a_request_start",
    method: request.method,
    request_id: request_id,
    timestamp: Time.now.iso8601
  }

  data[:params] = filter_sensitive_data(request.params) if @options[:log_params] && !request.params.empty?

  if @options[:include_context]
    data[:authenticated] = context.authenticated?
    data[:user] = context.user if context.user
  end

  @logger.send(@level, format_structured_log(data))
end

#log_structured_success(request, _context, result, duration, request_id) ⇒ Object (private)

Log structured request success



274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/a2a/server/middleware/logging_middleware.rb', line 274

def log_structured_success(request, _context, result, duration, request_id)
  data = {
    event: "a2a_request_success",
    method: request.method,
    request_id: request_id,
    duration: duration.round(3),
    timestamp: Time.now.iso8601
  }

  data[:response] = filter_sensitive_data(result) if @options[:log_response] && result

  @logger.send(@level, format_structured_log(data))
end

#remove_filtered_param(param) ⇒ Object

Remove filtered parameter

Parameters:

  • Parameter name to unfilter



110
111
112
# File 'lib/a2a/server/middleware/logging_middleware.rb', line 110

def remove_filtered_param(param)
  @options[:filtered_params].delete(param.to_s)
end

#validate_format!Object (private)

Validate the logging format

Raises:



118
119
120
121
122
# File 'lib/a2a/server/middleware/logging_middleware.rb', line 118

def validate_format!
  return if FORMATS.include?(@format)

  raise ArgumentError, "Invalid format: #{@format}. Must be one of: #{FORMATS.join(', ')}"
end