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
Related
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.
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
I have a class variable that I initialize, that later magically becomes uninitialized by itself... When developing locally on rails' default server (WEBrick), there is no problem. This only happens on nginx on ec2. Here's some code..
def TestController < ApplicationController
##classVariable = ""
def index
##classVariable = "What's up homie"
log(##classVariable)
end
def callThisMethodViaAJAXFromJavascript
log("reached this method")
log(##classVariable)
end
def log(str)
File.write("aValidPath", str)
end
end
Here's what I do: When I load the page on test/index, the index method executes, and properly logs ##classVariable as:
"What's up homie"
But when I call the method callThisMethodViaAJAXFromJavascript via AJAX from the frontend, my log file looks like:
"reached this method"
""
Again, this ONLY occurs on nginx on, ec2 (OS is ubuntu). When I run locally on WEBrick, this NEVER happens.
Any ideas? Thank you very much.
You don't say how you're using nginx (as a reverse proxy to some unicorn instances, with passenger etc) but either way you would typically have multiple instances of your application. Each one is a separate process so setting a class variable in one process has no effect on the other process.
Nginx will balance requests between the rails instances - so the index page is served by one instance and the ajax action will frequently be served by another process where the clas variable is still the empty string.
In development with webrick there is only one rails instance so you don't encounter this problem. I'm not sure what you were trying to do but class variables are not a good way to preserve state across requests
Per #Frederick's answer, I used the rails session instead to store the variable.
As a rule of thumb, it seems like you should NOT use global variables nor class variables in rails if your variables are changing.
The values stored in the session object are the same from any process in rails. Though the pointer to the session may be different, the values will remain the same. Here's another post about sessions that goes more into detail:
How is rails session shared among unicorn workers?
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
I need to store app specific configuration in rails. But it has to be:
reachable in any file (model, view, helpers and controllers
environment specified (or not), that means each environment can overwrite the configs specified in environment.rb
I've tried to use environment.rb and put something like
USE_USER_APP = true
that worked to me but when trying to overwrite it in a specific environment it wont work because production.rb, for instance, seems to be inside the Rails:Initializer.run block.
So, anyone?
Look at Configatron: http://github.com/markbates/configatron/tree/master
I have yet to use it, but he's actively developing it now, and looks quite nice.
I was helping a friend set up the solution mentioned by Ricardo yesterday. We hacked it a bit by loading the YAML file with something similar to this (going from memory here):
require 'ostruct'
require 'yaml'
require 'erb'
#config = OpenStruct.new(YAML.load_file("#{RAILS_ROOT}/config/config.yml"))
config = OpenStruct.new(YAML.load(ERB.new(File.read("#{RAILS_ROOT}/config/config.yml")).result))
env_config = config.send(RAILS_ENV)
config.common.update(env_config) unless env_config.nil?
::AppConfig = OpenStruct.new(config.common)
This allowed him to embed Ruby code in the config, like in Rhtml:
development:
path_to_something: <%= RAILS_ROOT %>/config/something.yml
The most basic thing to do is to set a class variable from your environment.rb. I've done this for Google Analytics. Essentially I want a different key depending on which environment I'm in so development or staging don't skew the metrics.
This is how I did it.
In lib/analytics/google_analytics.rb:
module Analytics
class GoogleAnalytics
##account_id = nil
cattr_accessor :account_id
end
end
And then in environment.rb or in environments/production.rb or any of the other environment files:
Analytics::GoogleAnalytics.account_id = "xxxxxxxxx"
Then anywhere you ned to reference, say the default layout with the Google Analytics JavaScript, it you just call Analytics::GoogleAnalytics.account_id.
I found a good way here
Use environment variables. Heroku uses this. Remember that if you keep configuration in the codebase, anyone with access to the code has access to any secret configuration (aws api keys, gateway api keys, etc).
daemontool's envdir is a good tool for setting configuration, I'm pretty sure that's what Heroku uses to give application their environment variables.
I have used Rails Settings Cached.
It is very simple to use, keeps your configuration values cached and allows you to change them dynamically.