googleapis / google-cloud-ruby

Google Cloud Client Library for Ruby
https://googleapis.github.io/google-cloud-ruby/
Apache License 2.0
1.35k stars 543 forks source link

HTTP request info in log entries #4000

Open jensljungblad opened 5 years ago

jensljungblad commented 5 years ago

Is your feature request related to a problem? Please describe.

I curious why Logging::Logger#write_entry doesn't add HTTP request info to the Entry that it creates. The rack env seems to be available in the logger (via the middleware) and there is even an HttpRequest object on the Entry, but it is never populated, it seems.

Describe the solution you'd like

Could request_method, user_agent etc on the Entry be populated via the information available in the @request_info_var variable?

Or, could there be a configuration that lets you modify the entry, and provides you with the rack env? Similar to how you configure labels.

config.entry = ->(entry, rack_env) { entry.httpRequest.request_method = rack_env["REQUEST_METHOD"] }
wojtha commented 4 years ago

For the time being I have added this monkey patch, together with lograge:

Rails.application.configure do
  config.lograge.enabled = true
  config.lograge.base_controller_class = ['ActionController::API', 'ActionController::Base']
  config.lograge.ignore_actions = [
    'ActiveStorage::DiskController#show',
    'ActiveStorage::BlobsController#show',
    'ActiveStorage::RepresentationsController#show',
  ]
  config.lograge.formatter = Lograge::Formatters::Raw.new
end

# Monkeypatch Google::Cloud::Logging::Logger to include the HTTP request info stored by the middleware.
# @see FEATURE REQUEST: https://github.com/googleapis/google-cloud-ruby/issues/4000
# @see https://github.com/googleapis/google-cloud-ruby/blob/master/google-cloud-logging/lib/google/cloud/logging/logger.rb
# @see https://github.com/googleapis/google-cloud-ruby/blob/master/google-cloud-logging/lib/google/cloud/logging/middleware.rb
module Google
  module Cloud
    module Logging
      class Logger
        alias original_write_entry write_entry

        protected

        ##
        # @private Write a log entry to the Stackdriver Logging service.
        def write_entry(severity, message)
          entry = Entry.new.tap do |e|
            e.timestamp = Time.current
            e.severity = gcloud_severity severity
            e.payload = message
          end

          actual_log_name = log_name
          info = request_info
          if info
            actual_log_name = info.log_name || actual_log_name
            unless info.trace_id.nil? || @project.nil?
              entry.trace = "projects/#{@project}/traces/#{info.trace_id}"
            end
            entry.trace_sampled = info.trace_sampled if entry.trace_sampled.nil?

            http_request_from_rails_env(entry, info)
          end

          writer.write_entries(entry, log_name: actual_log_name, resource: resource, labels: entry_labels(info))
        end

        ##
        # @private Assigns the information from environment to http request.
        #
        # @param [Google::Cloud::Logging::Entry] entry The log entry object
        # @param [Google::Cloud::Logging::Logger::RequestInfo] info A data about the request being handled by the
        #   current thread.
        #
        # @return [Google::Cloud::Logging::Entry]
        #
        def http_request_from_rails_env(entry, info)
          return unless info.env['action_controller.instance']

          request = info.env['action_controller.instance'].request
          response = info.env['action_controller.instance'].response

          # Google::Cloud::Logging::Entry::HttpRequest
          entry.http_request.request_method = request.method
          entry.http_request.url = request.original_url
          entry.http_request.user_agent = request.user_agent
          entry.http_request.size = request.content_length
          entry.http_request.remote_ip = request.remote_ip
          entry.http_request.response_size = response.body.length
          entry.http_request.referer = request.referer
          entry.http_request.status = response.status
          entry.http_request.cache_hit = nil
          entry.http_request.validated = nil
        end
      end
    end
  end
end