Include params/request information in Rails logger? - ruby-on-rails

I'm trying to get some more information into my Rails logs, specifically the requested URI or current params, if available (and I appreciate that they won't always be). However I just don't seem able to. Here's what I've done so far:
#config/environments/production.rb
config.logger = Logger.new(config.log_path)
config.log_level = :error
config.logger.level = Logger::ERROR
#config/environment.rb
class Logger
def format_message(level, time, progname, msg)
"**********************************************************************\n#{level} #{time.to_s(:db)} -- #{msg}\n"
end
end
So I can customize the message fine, yet I don't seem to be able to access the params/request variables here. Does anyone know if this is possible, and if so how? Or if there's a better way to get this information? (Perhaps even something Redis based?)
Thanks loads,
Dan

(Responding a long time after this was asked, but maybe it will help the next person.)
I just did something similar.
1) you need to override your logger separately from logging request-leve details. Looks like you've figured customizing your logger out. Answer is here:
Rails logger format string configuration
2) I log the request and response of all requests into my service. Note, that Rails puts a tonne of stuff into the headers, so just straight dumping the request or the headers is probably a bad idea. Also of note, my application is primarily accessed via an API. If yours is primarily a web-app, as I'm guessing most people's are, you probably don't want to inspect the response.body as it will contain your html.
class ApplicationController < ActionController::Base
around_filter :global_request_logging
...
def global_request_logging
http_request_header_keys = request.headers.keys.select{|header_name| header_name.match("^HTTP.*")}
http_request_headers = request.headers.select{|header_name, header_value| http_request_header_keys.index(header_name)}
logger.info "Received #{request.method.inspect} to #{request.url.inspect} from #{request.remote_ip.inspect}. Processing with headers #{http_request_headers.inspect} and params #{params.inspect}"
begin
yield
ensure
logger.info "Responding with #{response.status.inspect} => #{response.body.inspect}"
end
end
end

This should work! :) cheers.
logger.info({:user_agent =>
request.user_agent, :remote_ip =>
request.remote_ip}.inspect)
logger.info(params.inspect)
By the by.. This should be placed in your controllers action. Ex: If you place it in your create action it should also log the user_agent i.e the browser, remote_ip i.e the remote ip of the user and all the params.

you should look in the request class
like puts request.uri.
check here for more detail http://api.rubyonrails.org/classes/ActionController/AbstractRequest.html

Related

Rails Gateway endpoints loaded from yml/env file

I have been trying to implement the Rails API Gateway concept and I have been struggling with a way to define routes without too much of a hustle.
My current implementation looks something like this:
module ServicesEndpoints
class StackOverflow
def self.check_comment(id)
begin
RestClient.get endpoint + comment_path(id)
rescue => e
#if the server is not up or errors occur from our point of view there is no data about the driver
raise my_error
end
end
private
def self.endpoint
ENV['STACKOVERFLOW_SERVICE_ADDRESS']
end
def self.comment_path(id)
"/comments/#{id}"
end
end
end
However this means that everytime i have to add an endpoint i need 1 class and for every route 1 action. Thinking about this I was wondering if you couldn't do this in an yml/.env file and just load them. However even if I know how to put the endpoint in the .env file routes with params are a little tricky(I have no good idea on how to do it).
Has someone tackled this problem and if so how did you go about to solve it?

Rails: Model.find() or Model.find_by_id() to avoid RecordNotFound

I just realized I had a very hard to find bug on my website. I frequently use Model.find to retrieve data from my database.
A year ago I merged three websites causing a lot of redirections that needed to be handled. To do I created a "catch all"-functionality in my application controller as this:
around_filter :catch_not_found
def catch_not_found
yield
rescue ActiveRecord::RecordNotFound
require 'functions/redirections'
handle_redirection(request.path)
end
in addition I have this at the bottom of my routes.rb:
match '*not_found_path', :to => 'redirections#not_found_catcher', via: :get, as: :redirect_catcher, :constraints => lambda{|req| req.path !~ /\.(png|gif|jpg|txt|js|css)$/ }
Redirection-controller has:
def not_found_catcher
handle_redirection(request.path)
end
I am not sure these things are relevant in this question but I guess it is better to tell.
My actual problem
I frequently use Model.find to retrieve data from my database. Let's say I have a Product-model with a controller like this:
def show
#product = Product.find(params[:id])
#product.country = Country.find(...some id that does not exist...)
end
# View
<%= #product.country.name %>
This is something I use in some 700+ places in my application. What I realized today was that even though the Product model will be found. Calling the Country.find() and NOT find something causes a RecordNotFound, which in turn causes a 404 error.
I have made my app around the expectation that #product.country = nil if it couldn't find that Country in the .find-search. I know now that is not the case - it will create a RecordNotFound. Basically, if I load the Product#show I will get a 404-page where I would expect to get a 500-error (since #product.country = nil and nil.name should not work).
My question
My big question now. Am I doing things wrong in my app, should I always use Model.find_by_id for queries like my Country.find(...some id...)? What is the best practise here?
Or, does the problem lie within my catch all in the Application Controller?
To answer your questions:
should I always use Model.find_by_id
If you want to find by an id, use Country.find(...some id...). If you want to find be something else, use eg. Country.find_by(name: 'Australia'). The find_by_name syntax is no longer favoured in Rails 4.
But that's an aside, and is not your problem.
Or, does the problem lie within my catch all in the Application Controller?
Yeah, that sounds like a recipe for pain to me. I'm not sure what specifically you're doing or what the nature of your redirections is, but based on the vague sense I get of what you're trying to do, here's how I'd approach it:
Your Rails app shouldn't be responsible for redirecting routes from your previous websites / applications. That should be the responsibility of your webserver (eg nginx or apache or whatever).
Essentially you want to make a big fat list of all the URLs you want to redirect FROM, and where you want to redirect them TO, and then format them in the way your webserver expects, and configure your webserver to do the redirects for you. Search for eg "301 redirect nginx" or "301 redirect apache" to find out info on how to set that up.
If you've got a lot of URLs to redirect, you'll likely want to generate the list with code (most of the logic should already be there in your handle_redirection(request.path) method).
Once you've run that code and generated the list, you can throw that code away, your webserver will be handling the redirects form the old sites, and your rails app can happily go on with no knowledge of the previous sites / URLs, and no dangerous catch-all logic in your application controller.
That is a very interesting way to handle exceptions...
In Rails you use rescue_from to handle exceptions on the controller layer:
class ApplicationController < ActionController::Base
rescue_from SomeError, with: :oh_noes
private def oh_noes
render text: 'Oh no.'
end
end
However Rails already handles some exceptions by serving static html pages (among them ActiveRecord::RecordNotFound). Which you can override with dynamic handlers.
However as #joshua.paling already pointed out you should be handling the redirects on the server level instead of in your application.

How would one add headers to requests in Her?

I'm using Her to speak to an API I made so that I could retrieve information from it. I need an Authorization token passed in through the headers. How would I do this? The documentation doesn't show a solution anywhere, but it seems like a very much needed utility.
#sethetter's comment basically covered it, but since I recently did something similar, I thought I would include some code, which someone might find useful in future. So, to do this, you would create a request middleware, which adds the desired headers in the call method. I had to do something similar, and the result looked like this:
class Authentication < Faraday::Middleware
def call(env)
env[:request_headers]['auth-token'] = generate_token(env)
#app.call(env)
end
def generate_token(env)
# generate the token based on the request, if needed
end
end
And then, when you construct the API stack:
require 'authentication' #if you've defined it in a different file
#api = Her::API.new
#api.setup(:url => my_api_base_url) do |c|
c.use Authentication
# other middlewares as usual
end

How can I test the request object using a specific URL in application_helper_spec.rb?

I have a method defined in application_helper.rb that returns a canonical URL based on the current request. How can I mock or otherwise specify a complete URL for the controller?
# spec/helpers/application_helper_spec.rb
describe "#canonical_url" do
it "should return a path to an asset that includes the asset_host" do
# Given: "http://www.foo.com:80/asdf.asdf?asdf=asdf"
helper.canonical_url().should eq("http://www.foo.com/asdf.asdf")
end
end
# app/helpers/application_helper.rb
def canonical_url
"#{request.protocol}#{request.host}#{(request.port == 80) ? "" : request.port_string}#{request.path}"
end
EDIT:
Ultimately I want to test that canonical_url() returns the proper string for a bunch of different URLs, some with ports, some w/o, some with querystrings, some with paths, etc. Maybe that's overkill, but that is the end goal. I would like to both explicitly stub/mock/whatever the initial URL and then explicitly set the expectation in the matcher. I would love to be able to do this in one call, i.e. controller.request.url = 'http://www.foo.com:80/asdf.asdf?asdf=asdf' or request = ActionController::TestRequest.new :url => 'http://www.foo.com:80/asdf.asdf?asdf=asdf' but so far I have not found a single "hook" that allows me to do so. So that is the solution I'm looking for. How do I explicitly define the request URL for a given test.
I'd have done:
helper.request.stub(:protocol).and_return("http://")
helper.request.stub(:host).and_return("www.foo.com")
helper.request.stub(:port).and_return(80)
helper.request.stub(:port_string).and_return(":80")
helper.request.stub(:path).and_return("/asdf.asdf")
helper.canonical_url.should eq("http://www.foo.com/asdf.asdf")
The ultimate cause of this confusion lies in ActionPack:
ActionDispatch::TestRequest
ActionDispatch::Http::URL
e.g. if you set a port (ActionDispatch::TestRequest)
def port=(number)
#env['SERVER_PORT'] = number.to_i
end
e.g. then you read it (ActionDispatch::Http::URL)
def raw_host_with_port
if forwarded = env["HTTP_X_FORWARDED_HOST"]
forwarded.split(/,\s?/).last
else
env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
end
end
Setting SERVER_PORT will only take effect if you have no SERVER_NAME, HTTP_X_FORWARDED_HOST or HTTP_HOST already set.
My basic workaround for port settings was to add the port to the host - because request.port doesn't do what you want typically.
e.g. to set the port
request.host = 'example.com:1234'
The real answer is to read the code in ActionPack; it's fairly straightforward.
Very late to this party, but found it googling something similar.
How about:
allow_any_instance_of(ActionController::TestRequest).to receive(:host).and_return('www.fudge.com')
I appreciate allow_any_instance_of is sometimes frowned upon, but this does seem to get the job done.

Best way to deal with RoutingError in Rails 2.1.x?

I'm playing with the routing.rb code in Rails 2.1, and trying to to get it to the point where I can do something useful with the RoutingError exception that is thrown when it can't find the appropriate path.
This is a somewhat tricky problem, because there are some class of URLs which are just plain BAD: the /azenv.php bot attacks, the people typing /bar/foo/baz into the URL, etc... we don't want that.
Then there's subtle routing problems, where we do want to be notified: /artists/ for example, or ///. In these situations, we may want an error being thrown, or not... or we get Google sending us URLs which used to be valid but are no longer because people deleted them.
In each of these situations, I want a way to contain, analyze and filter the path that we get back, or at least some Railsy way to manage routing past the normal 'fallback catchall' url. Does this exist?
EDIT:
So the code here is:
# File vendor/rails/actionpack/lib/action_controller/rescue.rb, line 141
def rescue_action_without_handler(exception)
log_error(exception) if logger
erase_results if performed?
# Let the exception alter the response if it wants.
# For example, MethodNotAllowed sets the Allow header.
if exception.respond_to?(:handle_response!)
exception.handle_response!(response)
end
if consider_all_requests_local || local_request?
rescue_action_locally(exception)
else
rescue_action_in_public(exception)
end
end
So our best option is to override log_error(exception) so that we can filter down the exceptions according to the exception. So in ApplicationController
def log_error(exception)
message = '...'
if should_log_exception_as_debug?(exception)
logger.debug(message)
else
logger.error(message)
end
end
def should_log_exception_as_debug?(exception)
return (ActionController::RoutingError === exception)
end
Salt for additional logic where we want different controller logic, routes, etc.
Nooooo!!! Don't implement method_missing on your controller! And please try to avoid action_missing as well.
The frequently touted pattern is to add a route:
map.connect '*', :controller => 'error', :action => 'not_found'
Where you can show an appropriate error.
Rails also has a mechanism called rescue_action_in_public where you can write your own error handling logic -- we really should clean it up and encourage people to use it. PDI! :-)
There's the method_missing method. You could implement that in your Application Controller and catch all missing actions, maybe logging those and redirecting to the index action of the relevant controller. This approach would ignore everything that can't be routed to a controller, which is pretty close to what you want.
Alternatively, I'd just log all errors, extract the URL and sort it by # of times it occured.

Resources