class Gruf::Interceptors::Instrumentation::RequestLogging::Interceptor

Handles Rails-style request logging for gruf services.

This is added by default to gruf servers; if you have `Gruf.use_default_hooks = false`, you can add it back manually by doing:

Gruf::Instrumentation::Registry.add(:request_logging, Gruf::Instrumentation::RequestLogging::Hook)

Constants

LOG_LEVEL_MAP

Default mappings of codes to log levels…

Public Instance Methods

call() { || ... } click to toggle source

Log the request, sending it to the appropriate formatter

@return [String]

# File lib/gruf/interceptors/instrumentation/request_logging/interceptor.rb, line 64
def call(&block)
  return yield if options.fetch(:ignore_methods, [])&.include?(request.method_name)

  result = Gruf::Interceptors::Timer.time(&block)

  # Fetch log level options and merge with default...
  log_level_map = LOG_LEVEL_MAP.merge(options.fetch(:log_levels, {}))

  # A result is either successful, or, some level of feedback handled in the else block...
  if result.successful?
    type = log_level_map['GRPC::Ok'] || :debug
    status_name = 'GRPC::Ok'
  else
    type = log_level_map[result.message_class_name] || :error
    status_name = result.message_class_name
  end

  payload = {}
  if !request.client_streamer? && !request.bidi_streamer?
    payload[:params] = sanitize(request.message.to_h) if options.fetch(:log_parameters, false)
    payload[:message] = message(request, result)
    payload[:status] = status(result.message, result.successful?)
  else
    payload[:params] = {}
    payload[:message] = ''
    payload[:status] = GRPC::Core::StatusCodes::OK
  end

  payload[:service] = request.service_key
  payload[:method] = request.method_key
  payload[:action] = request.method_key
  payload[:grpc_status] = status_name
  payload[:duration] = result.elapsed_rounded
  payload[:thread_id] = Thread.current.object_id
  payload[:time] = Time.now.to_s
  payload[:host] = Socket.gethostname

  logger.send(type, formatter.format(payload, request: request, result: result))

  raise result.message unless result.successful?

  result.message
end

Private Instance Methods

formatter() click to toggle source

Determine the appropriate formatter for the request logging

@return [Gruf::Instrumentation::RequestLogging::Formatters::Base]

# File lib/gruf/interceptors/instrumentation/request_logging/interceptor.rb, line 146
def formatter
  unless @formatter
    fmt = options.fetch(:formatter, :plain)
    @formatter = case fmt
                 when Symbol
                   prefix = 'Gruf::Interceptors::Instrumentation::RequestLogging::Formatters::'
                   klass = "#{prefix}#{fmt.to_s.capitalize}"
                   fmt = klass.constantize.new
                 when Class
                   fmt = fmt.new
                 else
                   fmt
                 end
    raise InvalidFormatterError unless fmt.is_a?(Formatters::Base)
  end
  @formatter
end
hash_deep_redact!(hash, redacted_string) click to toggle source

Helper method to recursively redact the value of all hash keys

@param [Hash] hash Part of the hash of parameters to sanitize @param [String] redacted_string The custom redact string

# File lib/gruf/interceptors/instrumentation/request_logging/interceptor.rb, line 210
def hash_deep_redact!(hash, redacted_string)
  hash.each_key do |key|
    if hash[key].is_a? Hash
      hash_deep_redact!(hash[key], redacted_string)
    else
      hash[key] = redacted_string
    end
  end
end
logger() click to toggle source

@return [::Gruf::Logger]

# File lib/gruf/interceptors/instrumentation/request_logging/interceptor.rb, line 113
def logger
  @logger ||= (options.fetch(:logger, nil) || Gruf.logger)
end
message(request, result) click to toggle source

Return an appropriate log message dependent on the status

@param [Gruf::Controllers::Request] request @param [Gruf::Interceptors::Timer::Result] result @return [String] The appropriate message body

# File lib/gruf/interceptors/instrumentation/request_logging/interceptor.rb, line 124
def message(request, result)
  return "[GRPC::Ok] (#{request.method_name})" if result.successful?

  "[#{result.message_class_name}] (#{request.method_name}) #{result.message.message}"
end
redact!(parts = [], idx = 0, params = {}, redacted_string = 'REDACTED') click to toggle source

Helper method to recursively redact based on the blocklist

@param [Array] parts The blocklist. ex. 'data.schema' -> [:data, :schema] @param [Integer] idx The current index of the blocklist @param [Hash] params The hash of parameters to sanitize @param [String] redacted_string The custom redact string

# File lib/gruf/interceptors/instrumentation/request_logging/interceptor.rb, line 188
def redact!(parts = [], idx = 0, params = {}, redacted_string = 'REDACTED')
  return unless parts.is_a?(Array) && params.is_a?(Hash)

  return if idx >= parts.size || !params.key?(parts[idx])

  if idx == parts.size - 1
    if params[parts[idx]].is_a? Hash
      hash_deep_redact!(params[parts[idx]], redacted_string)
    else
      params[parts[idx]] = redacted_string
    end
    return
  end
  redact!(parts, idx + 1, params[parts[idx]], redacted_string)
end
sanitize(params = {}) click to toggle source

Redact any blocklisted params and return an updated hash

@param [Hash] params The hash of parameters to sanitize @return [Hash] The sanitized params in hash form

# File lib/gruf/interceptors/instrumentation/request_logging/interceptor.rb, line 170
def sanitize(params = {})
  blocklists = (options.fetch(:blocklist, []) || []).map(&:to_s)
  redacted_string = options.fetch(:redacted_string, nil) || 'REDACTED'
  blocklists.each do |blocklist|
    parts = blocklist.split('.').map(&:to_sym)
    redact!(parts, 0, params, redacted_string)
  end
  params
end
status(response, successful) click to toggle source

Return the proper status code for the response

@param [Object] response The response object @param [Boolean] successful If the response was successful @return [Boolean] The proper status code

# File lib/gruf/interceptors/instrumentation/request_logging/interceptor.rb, line 137
def status(response, successful)
  successful ? GRPC::Core::StatusCodes::OK : response.code
end