Dynamically override destination file of all Logger output in Rails - ruby-on-rails

I am using the Apartment gem for a multi tenant Rails 5.2 app. I'm not sure that this even matters for my question but just giving some context.
Is there a way to override the Rails logger and redirect every single log entry to a file based on the tenant (database) that is being used?
Thinking... is there a method I can monkeypatch in Logger that will change the file written to dynamically?
Example: I want every error message directed to a file for that day. So at the end of a week there will be 7 dynamically generated files for errors that occurred on each specific day.
Another example: Before you write any server log message check if it is before 1pm. If it is before 1pm write it to /log/before_1.log ... if it is after 1pm write it to /log/after_1.log
Silly examples... but I want that kind of dynamic control before any line of log is written.
Thank you!

Usually the logger is usually configured per server (or per environment really) while apartment sets tenants per request - which means that in practice its not really going to work that well.
You can set the logger at any point by assigning Rails.logger to a logger instance.
Rails.logger = Logger.new(Rails.root.join('log/foo.log'), File::APPEND)
# or for multiple loggers
Rails.logger.extend(Logger.new(Rails.root.join('log/foo.log'), File::APPEND))
However its not that simple - you can't just throw that into ApplicationController and think that everything is hunky-dory - it will be called way to late and most of the entries with important stuff like the request or any errors that pop up before the controller will end up in the default log.
What you could do is write a custom piece of middleware that switches out the log:
# app/middleware/tenant_logger.rb
class TenantLogger
def initialize app
#app = app
end
def call(env)
file_name = "#{Appartment::Tenant.current}.log"
Rails.logger = Logger.new(Rails.root.join('log', file_name), File::APPEND)
#app.call(env)
end
end
And mount it after the "elevator" in the middleware stack:
Rails.application.config.middleware.insert_after Apartment::Elevators::Subdomain, TenantLogger
However as this is pretty far down in the middleware stack you will still miss quite a lot of important info logged by the middleware such as Rails::Rack::Logger.
Using the tagged logger as suggested by the Rails guides with a single file is a much better solution.

Related

logger in google-cloud-logging is not working for rails

I'm currently working on the GCP and deployed my rails app on the GCP instance.
I'm currently using https://github.com/brendeschuijmert/google-cloud-ruby/tree/master/google-cloud-logging for the compute engine.
When I use this on the rails application.
Rails.logger.info "Hello World" works well.
But logger.warn "Hola Mundo" is not working.
I want someone shade some light on this problem.
Thanks
If you're trying to call logger from outside of a controller/model or some other file that is a part of Rails' structure - you will have to explicitly create a logger for yourself with:
logger = Logger.new(STDOUT) # Or wherever you want to log to
However if Rails.logger is working, then you likely just need an alias like logger = Rails.logger to allow you to use logger.warn without the Rails namespace prefix. You're probably in a file that doesn't already have a helper that is aliasing that for you.
Some more digging in the API reveals that the logger stems from ActiveSupport::LogSubscriber - several classes like ActionController include child LogSubscribers that inherit from that module and hence have the logger method available to them. You can manually alias it to Rails.logger to have it work for you wherever you are trying to invoke it.
Source: https://api.rubyonrails.org/classes/ActiveSupport/LogSubscriber.html#method-c-logger

Apartment gem - Adding current database to all server logs

I am using the Apartment gem to switch the tenant (database) being used for a multi tenancy Rails application.
In my server logs I would like to output the current tenant (database) being used for every single line in the log file.
When I do rails s the server never actually starts with the code below that is in the initializers directory. The server just hangs... so odd. No error message and no running server. If I take out #{Apartment::Tenant.current} below everything is fine... but... I really want to know the current tenant (database) in my log files.
/initializers/log_formatting.rb:
class ActiveSupport::Logger::SimpleFormatter
def call(severity, time, progname, msg)
"#{Apartment::Tenant.current} #{msg.strip} (pid:#{$$})\n"
end
end
Any ideas on how to get the current tenant (database) being used output to every line of my log file?
Thank you!
I would suggest you to use log_tags.
From the rails documentation :
config.log_tags accepts a list of: methods that the request object responds to, a Proc that accepts the request object, or something that responds to to_s. This makes it easy to tag log lines with debug information like subdomain and request id - both very helpful in debugging multi-user production applications.
You can add this configuration in application.rb or production.rb whichever fits your need.
For ex: config.log_tags = [ :subdomain, :request_id, lambda { |request| request.headers["tenant_name"] } ]
Note: In case you are adding this for development environment and you are running on your_subdomain.localhost:3000 then subdomain won't be present as localhost doesn't support subdomains. There are some workarounds like modifying /etc/hosts file but i won't recommend it. The more cleaner solution is to use your_subdomain.lvh.me:3000

How to (cleanly) log to a different file per proccess with Ruby on Rails on Phussion Passenger?

So I want to analize with RequestLogAnalyzer the production logs of a Rails 4 application that runs as a fixed pool of proccesses with Phussion Passenger on Nginx.
The problem is that because the the log file is shared sometimes you get interleaved lines of differents requests and RequestLogAnalyzer disposes that lines. So I said, oh let's fix this simple issue it should be quick.. well still here I am my friends.
First I 've attemped this simple change in config/environments/production.rb
config.logger = ActiveSupport::Logger.new(File.join(Rails.root, 'log', "#{Rails.env}-#{Date.today.strftime('%Y_%W')}-#{Process.pid}.log"))
But only one log file was created.. and the PIDs wasn't of any of the app group proccess that passenger reported via 'passenger-status'.. so I dig dipper and found about the default smart spawn method (https://www.phusionpassenger.com/library/indepth/ruby/spawn_methods/#unintentional-file-descriptor-sharing) and obviously when the config was set it was before the child spawn proccess is created.. so wrong attemp.
But in the same article it explained about passenger hooks.. I google a little and voila:
http://pmatsinopoulos.github.io/blog/2016/02/19/making-rails-logger-use-one-log-file-per-process-with-phusion-passenger/
But wait.. what this code is doing, monkey patching and messing this way with the internal workings of the Logging class.. nono there should be something cleaner.
So next I've created a new initializar file with:
if Rails.env.production? && defined?(PhusionPassenger)
PhusionPassenger.on_event(:starting_worker_process) do |forked|
Rails.logger = ActiveSupport::Logger.new(File.join(Rails.root, 'log', "#{Rails.env}-#{Date.today.strftime('%Y_%W')}-#{Process.pid}.log"))
end
end
Simple enough right? Well that is 'kindaof' working: I get different log files per proccess (yeah) but also production.log is still created and logs dumped there..
It looks like someone is caching Rails.logger?? Any idea what is happening here..
In our old rails 4 app we add pid to log.
we add config/initializers/logger.rb
class ActiveSupport::Logger
class SimpleFormatter < Logger::Formatter
def call(severity, timestamp, progname, msg)
"[#{$$}] [#{timestamp.strftime('%d.%m.%Y %H:%M:%S')}] #{severity}: #{msg}\n"
end
end
end

Where is a good place to initialize an API?

I wanted to use this api: https://github.com/coinbase/coinbase-ruby and the first step is to initialize the API, like this:
coinbase = Coinbase::Client.new(ENV['COINBASE_API_KEY'], ENV['COINBASE_API_SECRET'])
I was wondering what the best place to put this code is, and how would I access it if I put it "there"? I want this variable (coinbase) to be accessible ANYWHERE in the application.
Thanks!
The answer to this question really depends on your use case and your approach. My geral recommendation, however, is to create a Service Object (in the DDD sense) (see the section named "Domain Objects Should Not Know Anything About Infrastructure Underneath" in that link), that handles all communication with the Coinbase API. And then, within this service object, you can simply initialize the Coinbase::Client object once for however many times you call into it. Here's an example:
# app/services/coinbase_service.rb
class CoinbaseService
cattr_reader :coinbase_client, instance_accessor: false do
Coinbase::Client.new(ENV['COINBASE_API_KEY'], ENV['COINBASE_API_SECRET'])
end
def self.do_something
coinbase_client.do_something_in_their_api
end
def self.do_something_else
coinbase_client.do_something_else_in_their_api
end
end
So then you might do, e.g.:
# From MyController#action_1
if CoinbaseService.do_something
# ...
else
# ...
end
Or:
# From MyModel
def do_something
CoinbaseService.do_something_else
end
To get the service object working, you may need to add app/services to your autoload paths in application.rb file. I normally just add this:
# config/application.rb
config.autoload_paths += %W(#{config.root}/app)
I find this Service Object approach to be very beneficial organizationally, more efficient (only 1 invocation of the new Coinbase client needed), easier to test (easy to mock-out calls to Coinbase::Client), and simply joyful :).
One way to go about having a global variable can be done as similar as initializing redis in a Rails application by creating an initializer in config/initializers/coinbase.rb with:
$coinbase = Coinbase::Client.new(ENV['COINBASE_API_KEY'], ENV['COINBASE_API_SECRET'])
Now, you can access $coinbase anywhere in the application!
In the file config/initializers/coinbase.rb
Rails.application.config.after_initialize do
CoinbaseClient = Coinbase::Client.new(
Rails.application.credentials.coinbase[:api_key],
Rails.application.credentials.coinbase[:api_secret])
end
In place of the encrypted credentials, you could also use environment variables: ENV['COINBASE_API_KEY'], ENV['COINBASE_API_SECRET']
The above will make the constant CoinbaseClient available everywhere in your app. It will also ensure all your gems are loaded before the client is initialized.
Note: I am using Rails 6.1.4.4, and Ruby 2.7.5

Explain to me how config works in Rails

I have a Rails 3 application, call it "MyApp". In my config\environments\production.rb file I see such things as
MyApp::Application.configure do
config.log_level = :info
config.logger = Logger.new(config.paths.log.first, 'daily')
...or...
config.logger = Logger.new(Rails.root.join("log",Rails.env + ".log"),3,20*1024*1024)
So, questions are focusing on terminology and wtf they mean... (or point me to some site ,I have looked but not found, to explain how this works.)
MyApp is a module?
MyApp::Application is a ...? What, a module too?
MyApp::Application.configure is a method?
config is a variable? How do I see it in console?
config.logger is a ???
config.paths.log.first is a ...??
--in console I can see "MyApp::Application.configure.config.paths.log.first" but don't know what that means or how to extract info from it!?!
Is this too much for one question? :)
I have looked at the tutorial http://guides.rubyonrails.org/configuring.html but it jumps right into what things do.
A six sided question! Oh my. Let's ahem roll.1 Here's hoping I receive 6 times the upvotes for it then? :)
1. MyApp is a module?
Yes, it's a module. It acts as a "container" for all things pertaining to your application. For instance you could define a class like this:
module MyApp
class MyFunClass
def my_fun_method
end
end
end
Then if someone else has a MyFunClass, it won't interfere with your MyFunClass. It's just a nice way of separating out the code.
2. MyApp::Application is a ...? What, a module too?
MyApp::Application is actually a class, which inherits from Rails::Application. This does a quite a lot of things, including setting up the Rails.application object which is actually an instance of MyApp::Application that you can do all sorts of fun things on like making requests to your application (in a rails console or rails c session). This code for instance would make a dummy request to the root path of your application, returning a 3-sized Array which is just a plain Rack response:
Rails.application.call(Rack::MockRequest.env_for("/"))
You can also get the routes for your application by calling this:
Rails.application.routes
The main purpose of defining MyApp::Application is not these fun things that you'll probably never use, but rather so that you can define application-specific configuration inside config/application.rb. Things like what parameters are filtered, the time zone of the application or what directories should be autoloaded. These are all covered in the Configuration Guide for Rails.
3. MyApp::Application.configure is a method?
Indeed it is a method, and it allows you to add further configuration options to your application's configuration after config/application.rb has been loaded. You've probably seen this used in config/environments/development.rb or one of the other two files in that directory, but basically they all use the same options as shown in that Configuration Guide linked to earlier.
4. config is a variable? How do I see it in console?
The config "variable" is actually a method defined within the code used for Rails::Application and returns quite simply a configuration object which stores the configuration for the application.
To access it in the console, just use Rails.application.config. This will return quite a large Rails::Application::Configuration object for your viewing pleasure.
5. config.logger is a ???
The method you're referring to, I assume, comes from this line in config/environments/production.rb:
# Use a different logger for distributed setups
# config.logger = SyslogLogger.new
The method in this example is not config.logger, but rather config.logger=, which is referred to as a "setter" method in Ruby-land. The one without the equal sign is referred to as a "getter". This method sets up an alternative logger for the production environment in Rails, which then can be accessed by using Rails.logger within the console or the application itself.
This is useful if you want to output something to the logs, as you can simply call this code:
Rails.logger.info("DEBUG INFO GOES HERE")
6. config.paths.log.first is a ...?? --in console I can see "MyApp::Application.configure.config.paths.log.first" but don't know what that means or how to extract info from it!?!
Within a Rails application, you can modify the locations of certain directories. And so, this config.paths method is a way of keeping track of where these directories map to. In my entire Rails life I have never had to use or modify this variable and that can mean either one of two things:
It's not used often by Rails programmers, or;
I don't live a very varied life.
Interpret it as you will. My main point is that you're probably never going to use it either.
I hope these help you understand Rails a little more!
1 Terrible dice joke.
MyApp is a module, it's a namespace including an app you'll launch, see next line
MyApp::Application is a Class and you're running it's instances when running a Rails app
MyApp::Application.configure is a method. It passes all instructions to the class. See Ref.
config is a method or an instance variable (when set) which belongs through inheritance to Rails::Application::Configuration. See Ref.
You can see it in console doing: MyApp::Application.config
config.logger doesn't exist until you define it, so it's a Logger instance. See Ref.
config.paths.log is a Rails::Paths::Path
you can access it in console using: MyApp::Application.config.paths.log

Resources