Logging in Ruby/Rails - ruby-on-rails

I have the following code:
def get_request(resource)
request = Typhoeus::Request.new("#{#BASE_URL}#{resource}",
userpwd: "#{#USER}:#{#PWD}",
headers: { 'Content-Type' => "application/x-www-form-urlencoded"})
response = request.run.body
puts response
end
Instead of puts response, I want to log the entire response. What's the best/most efficient way to do that? Regardless of what response is, it should be logged. I feel like opening a file, writing to it and closing it every time this method is used would be pretty inefficient. Is there a better way?

If you are using Rails as the tag supposes, you can use
Rails.logger
to use the default Rails logger. Here's an example.
Rails.logger.info response.body

Related

How to create requests params for json api resources in requests tests?

I'm creating a API with Rails 6 and JSON API Resource in order to learn more. I got stuck with requests tests. What I'm doing for now is using the gem json_matchers to test the response of my endpoint, but my real problem is to build the body of requests for: post, put and patch.
Hi, thanks for you attention.
Here is the deal, I started adding to my RSpec files all the json needed for the tests, but these files ended up getting too big. So I thought, how can I makes this to stay more lean?
Then I copy all the json in my tests to json files and importing when needed, for example:
# RSpec Helper
def request_json(json_name)
request_directory = "#{Dir.pwd}/spec/support/api/requests"
request_path = "#{request_directory}/#{json_name}.json"
File.read(request_path)
end
# Example of params for user request
let(:params) { request_json("user") }
I thoutgh it was a great idea, but then I ran into two problems: I need to create a file for every request test and... how I will modify same value of this json file at runtime? For example, I create a object using FactoryBot and now I need to use the ID of this object in my request for a update. How can I do that?
Using regex?
let(:params) do
json = request_json("user")
json.gsub(/\"id\": \"1\"/, "\"id\": \"#{id}\"")
end
Ok... It works, but I don't like it! I think that this can turn to be a real mess. Another options was to convert this to hash and then json again... Nope, nope, nope, don't like it either.
Now I'm trying to create something more dynamic using FactoryBot and Faker. For now, with the code below, I can pass a factory name and receive a perfect json body for a post.
def serializer_for(resource)
{
"data": {
"type": resource.to_s.tableize,
"attributes": create_attributes_for(resource),
}
}.to_json
end
def create_attributes_for(resource)
attributes = attributes_for(resource)
attributes.reduce({}) do |hash, element|
hash.update(standardize_json_key() => element.last.to_s)
end
end
def standardize_json_key(symbol)
symbol.to_s.gsub("_", "-")
end
But then I began to think about the challenges of this approach:
Post/Put/Patch with relationships?(has_one or has_many)
How I'll add the ID field for put/path?
Add how I'll add the upload files when needed?
So... returning to my question: How can I prepare the json body for the request using factory bot (using even traits) with all the concerns above?
If you have/know a better answer for this problem, share with, please. If not, I'll try to create a gem for this.
Appreciate your time, thanks!

Read only portion of a remote file with rails [duplicate]

It seems like the methods of Ruby's Net::HTTP are all or nothing when it comes to reading the body of a web page. How can I read, say, the just the first 100 bytes of the body?
I am trying to read from a content server that returns a short error message in the body of the response if the file requested isn't available. I need to read enough of the body to determine whether the file is there. The files are huge, so I don't want to get the whole body just to check if the file is available.
This is an old thread, but the question of how to read only a portion of a file via HTTP in Ruby is still a mostly unanswered one according to my research. Here's a solution I came up with by monkey-patching Net::HTTP a bit:
require 'net/http'
# provide access to the actual socket
class Net::HTTPResponse
attr_reader :socket
end
uri = URI("http://www.example.com/path/to/file")
begin
Net::HTTP.start(uri.host, uri.port) do |http|
request = Net::HTTP::Get.new(uri.request_uri)
# calling request with a block prevents body from being read
http.request(request) do |response|
# do whatever limited reading you want to do with the socket
x = response.socket.read(100);
# be sure to call finish before exiting the block
http.finish
end
end
rescue IOError
# ignore
end
The rescue catches the IOError that's thrown when you call HTTP.finish prematurely.
FYI, the socket within the HTTPResponse object isn't a true IO object (it's an internal class called BufferedIO), but it's pretty easy to monkey-patch that, too, to mimic the IO methods you need. For example, another library I was using (exifr) needed the readchar method, which was easy to add:
class Net::BufferedIO
def readchar
read(1)[0].ord
end
end
Shouldn't you just use an HTTP HEAD request (Ruby Net::HTTP::Head method) to see if the resource is there, and only proceed if you get a 2xx or 3xx response? This presumes your server is configured to return a 4xx error code if the document is not available. I would argue this was the correct solution.
An alternative is to request the HTTP head and look at the content-length header value in the result: if your server is correctly configured, you should easily be able to tell the difference in length between a short message and a long document. Another alternative: set the content-range header field in the request (which again assumes that the server is behaving correctly WRT the HTTP spec).
I don't think that solving the problem in the client after you've sent the GET request is the way to go: by that time, the network has done the heavy lifting, and you won't really save any wasted resources.
Reference: http header definitions
I wanted to do this once, and the only thing that I could think of is monkey patching the Net::HTTP#read_body and Net::HTTP#read_body_0 methods to accept a length parameter, and then in the former just pass the length parameter to the read_body_0 method, where you can read only as much as length bytes.
To read the body of an HTTP request in chunks, you'll need to use Net::HTTPResponse#read_body like this:
http.request_get('/large_resource') do |response|
response.read_body do |segment|
print segment
end
end
Are you sure the content server only returns a short error page?
Doesn't it also set the HTTPResponse to something appropriate like 404. In which case you can trap the HTTPClientError derived exception (most likely HTTPNotFound) which is raised when accessing Net::HTTP.value().
If you get an error then your file wasn't there if you get 200 the file is starting to download and you can close the connection.
You can't. But why do you need to? Surely if the page just says that the file isn't available then it won't be a huge page (i.e. by definition, the file won't be there)?

Getting accurate timings in logs for streaming responses?

Using Rails 3.2.14. I'm streaming a controller action response, as follows.
def my_controller_action
stream = StreamingClass.new(*args) #responds to each
response.sending_file= true
headers.merge!(
'Content-Disposition' => 'inline',
'Content-Transfer-Encoding' => 'binary',
'Cache-Control' => 'no-cache'
)
self.status = 200
self.content_type= 'application/json'
self.response_body = stream
end
The streaming works just fine, but the problem is that the controller action returns before the streaming is completed (i.e. before each is called on the 'stream' object). It basically returns immediately after assigning the 'stream' object to self.response_body.
I'm using the lograge gem to tidy up our logging. Lograge basically subscribes to the 'process_action.action_controller' notifications. It is logging the timings (i.e. duration, db_runtime, etc...) based on the actual controller return time, without tracking any time spent on the stream object code.
The heavy lifting occurs in a StreamingClass method, but I'm completely missing this info from the logs. Is there some way to include the streaming response timings in the logs?
I'm running into this same issue also. It seems to me that the only way to tell when the streaming is complete is to wrap the response_body in a proxy object whose close method includes additional logic which records the stats you need. You could probably use something like this:
class BodyProxy
def initialize(body)
#body = body
end
def each(&block)
#body.each(&block)
end
def close
#body.close if #body.respond_to?(:close)
# Your code here. Probably something involving `Time.now`
end
end
This works because the Rack specification requires that close be called on the body "after iteration".
I'm unfamiliar with Lograge, so I don't know how you would send this information to that gem, but this should be enough to get you started.

Rails: Getting the JSON object after making a post request

I'm a beginner Rails programmer and even though this question might be too easy, I love to know if it's possible and if it is how can I accomplish that?
My question inside the index how can I make a get request to a link and assign it's JSON response to an object that I will be later using on. The syntax(not correct) I had in my mind was something like;
people=Make_A_Get_Request("http://people.com") //It will return in JSON
#peopleName=people['name']
I know that it's not true but is there like any method I can apply in Rails like the one above to make a get request to a link and assign its JSON response to an object in my rails function
Try something like below
def index
uri = URI('http://people.com/path/to/request')
response = Net::HTTP.get(uri)
data = JSON.parse(response.body)
#then you can play like data["name"]
rescue Exception => e
logger.info "Unable to do something due to #{e.message}"
end

Include params/request information in Rails logger?

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

Resources