#Here is how I have delayed job set up.
Delayed::Worker.backend = :active_record
#Delayed::Worker.logger = Rails.logger
Delayed::Worker.logger = ActiveSupport::BufferedLogger.new("log/
##{Rails.env}_delayed_jobs.log", Rails.logger.level)
Delayed::Worker.logger.auto_flushing = 1
class Delayed::Job
def logger
Delayed::Worker.logger
end
end
if JobsCommon::check_job_exists("PeriodicJob").blank?
Delayed::Job.enqueue PeriodicJob.new(), 0, 30.seconds.from_now
end
#end
#Here is my simple job.
class PeriodicJob
def perform
Rails.logger.info "Periodic job writing #{Time.now}"
Delayed::Job.enqueue PeriodicJob.new(), 0,
30.seconds.from_now
end
end
I don't see any log messages from delayed job in my rails logs or delayed job log file, the only messages I see are jobs starting/success/failure in the delayed_jobs.log file.
this is causing big problems, including detecting bugs and memory leaks in workers almost impossible! Please help!
We've gotten it to work on Rails 3/Delayed Job 2.0.3 by hacking Rails.logger itself to use a different log file (the one we want for delayed_job entries) and also setting the delayed job logger to use the exact same object:
file_handle = File.open("log/#{Rails.env}_delayed_jobs.log", (File::WRONLY | File::APPEND | File::CREAT))
# Be paranoid about syncing, part #1
file_handle.sync = true
# Be paranoid about syncing, part #2
Rails.logger.auto_flushing = true
# Hack the existing Rails.logger object to use our new file handle
Rails.logger.instance_variable_set :#log, file_handle
# Calls to Rails.logger go to the same object as Delayed::Worker.logger
Delayed::Worker.logger = Rails.logger
If the above code doesn't work, try replacing Rails.logger with RAILS_DEFAULT_LOGGER.
This may be a simple workaround but it works well enough for me:
system("echo #{your message here} >> logfile.log")
Simple but works
I have it working with the following setup in initializer:
require 'delayed/worker'
Delayed::Worker.logger = Rails.logger
module Delayed
class Worker
def say_with_flushing(text, level = Logger::INFO)
if logger
say_without_flushing(text, level)
logger.flush
end
end
alias_method_chain :say, :flushing
end
end
i simply did this:
/config/environments/development.rb
MyApp::Application.configure do
[...]
[...]
[...]
Delayed::Worker.logger = Rails.logger
end
In every next request you do the mail will be appear on the log.
NOTE: Sometimes you have to refresh the page to the mail be logged on the log. Don't forget to restart the server ;)
DelayedJob does not seem to output if there is something wrong:
1- Non-active record classes need to be required and initialized:
How:
Create a file config/initializers/load_classes_for_dj.rb
Add to it the lines:
require 'lib/libtest/delayed_test.rb'
DelayedTest
Note that if you have '#{config.root}/lib/libtest' in config.autoload_paths in config/application.rb, you do not need to do the require.
Source:
Rails Delayed Job & Library Class
2- Classes that implement the Singleton module won't work by calling:
SingletonClass.instance.delay.sayhello
In order to fix that, do the following:
class SingletonClass
include Singleton
# create a class method that does the call for you
def self.delayed_sayhello
SingletonClass.instance.sayhello
end
def sayhello
# this is how you can actually write to delayed_job.log
# https://stackoverflow.com/questions/9507765/delayed-job-not-logging
Delayed::Worker.logger.add(Logger::INFO, "hello there")
end
end
In order to call the delayed class method, do the following:
SingletonClass.delay.delayed_sayhello
Yes, I am calling .delay on a class here. Since classes in Ruby are also objects, the call is valid here and allows me to access the class method "delayed_sayhello"
3- Do not pass ActiveRecord objects or some complex objects to your call but rather pass ids, look up the objects in the database in your delayed method, and then do your work:
DO NOT DO:
DelayedClass.new.delay.do_some_processing_on_album Album.first
DO THIS INSTEAD:
DelayedClass.new.delay.do_some_processing_on_album Album.first.id
and inside DelayedClass do_some_processing_on_album , do
a = Album.find_by_id id
There was a stackoverflow post about this that I saw a while ago -- not sure which :-)
4- For completion, this is how to do mailers (do not call the deliver method):
Notifier.delay.signup(user_id)
As per 3, do not pass the user's object but rather their id, do the lookup inside the signup method.
Now once you've ensured you have followed the guidelines above, you can use:
Delayed::Worker.logger.add(Logger::INFO, "hello")
If you are still facing issues, I suggest you break down your problem:
a- Create a new class
b- Make sure you have it included and initialized as per step 1
c- Add logging from step 4 and try calling MyNewClass.new.delay to see if it works
I hope this helps you guys :-)
Don't forget to change the ActiveRecord::Base.logger
Delayed::Worker.logger = Logger.new("log/delayed_job.log", 5, 104857600)
if caller.last =~ /script\/delayed_job/ or (File.basename($0) == "rake" and ARGV[0] =~ /jobs\:work/)
ActiveRecord::Base.logger = Delayed::Worker.logger
end
More detailed answer: Have delayed_job log "puts", sql queries and jobs status
Related
I'm using Rails' ActiveJob, and one of my jobs take a raw email as input. When debugging, this can result in a huge amount of noise in my application log. How can I avoid that?
[ActiveJob] Enqueued EmailParserJob (Job ID: 9678f343-c876-4f9f-9cc7-db440634e178) to DelayedJob(default) with arguments: "NOISE"
See https://github.com/rails/rails/blob/4-2-stable/activejob/lib/active_job/logging.rb#L10
ActiveJob::Base.logger = Logger.new(nil)
One thing that may be useful to note here: In any instance of a class that is subclassed from (Rails 5.1) ActiveJob::Base (or, any class instance called by a class subclassed from ActiveJob::Base) The normal Rails.logger.info('log this') commands are going to get logged to the rails console (presumably via STDOUT).
I haven't quite figured out the mechanism that causes this hijacking of Rails.logger, but you can switch to ActiveJob::Base.logger and use the knowledge of this: (https://github.com/rails/rails/blob/b205ea2dc6c70b2b8e2134640e3056ed33fdc6be/activejob/lib/active_job/logging.rb#L13) to change the behavior as you wish.
So, this allows you to log as you want:
1) Include require "active_job/logging" in your application.rb
2) In config/development.rb (or whatever environments you want) include this line:
config.active_job.logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new("log/#{Rails.env}.log"))
3) Any logging inside of subclasses of ActiveJob, use this for logging:
ActiveJob::Base.logger.info('(MyJob) Inside of a job but not going to STDOUT')
If anyone can point out the code that explains why Rails.logger.info behaves differently when inside of an ActiveJob class that would be some good reading.
It seems the only way is to override ActiveJob's internal logging method:
class ActiveJob::Logging::LogSubscriber
private def args_info(job)
''
end
end
Put it somewhere into app/initializers/active_job_logger_patch.rb.
I used after_initialize to hook beforehand. It turned out to work only in perform_start method but not enqueue.
Using on_load method to hook works. I think it's the lazyload feature in Rails causing the class to be loaded after the override.
ActiveSupport.on_load :active_job do
class ActiveJob::Logging::LogSubscriber
private def args_info(job)
# override this method to filter arguments shown in app log
end
end
end
From Rails 6.1, you can turn off the logging with log_arguments like the below. This will turn off logging the arguments for all the jobs derived from ApplicationJob, You can also set it on per job basis.
class ApplicationJob < ActiveJob::Base
self.log_arguments = false
end
Reference:
https://github.com/rails/rails/pull/37660
It looks like they added the feature Add an option to disable logging for jobs with sensitive arguments in Rail 6. This would prevent any arguments from appearing in the log lines.
Edit: To use this with ActionMailer, using a custom mailer job might work, haven't tested this myself yet.
Found the following recommendation here:
ActiveJob::Base.logger = Logger.new(IO::NULL)
Seems to be better than passing a nil
I've a class defined as such:
class PublicationJob < ActiveJob::Base
def self.jobs
#jobs ||= Hash{|h, k| h[k] = []}
end
def self.register(format, job_class)
jobs[format] << job_class
end
# [...]
end
To register different job classes, I put in an initializer:
PublicationJob.register(:tex, SaveJob)
PublicationJob.register(:saved_tex, TexJob)
#...
The in the rails console I try:
PublicationJob.jobs
#> {:tex => [SaveJob], :saved_tex => [TexJob]}
But if I exit the console (Ctrl-D) then restart it, at some point the hash will be empty!
Why is the class variable reset in this case?
I use rails 4.2.1 and spring, and I know that if I kill/stop spring it works again for some time. Is it related to spring?
Okay, so this was entirely Spring related, and I fixed it by removing spring.
Thanks to #NekoNova who pointed me to the right part of the documentation, I found that:
This saves off the first version of the User class, which will not be the same object as User after the code has been reloaded:
[...]
So to avoid this problem, don't save off references to application constants in your initialization code.
In other words, I can't initialize my classes using initializers, because althought it'll work in production, it won't work in development.
I know this is rather old, but I have encountered this issue a couple of times, and feel that you don't have to abandon spring if you set class level variables at initialization.
All you need to do is re-assign them in a spring ".after_fork" block. So for the above issue, place in a "config/spring.rb" file, the following:
if ("Spring".constantize rescue nil)
Spring.after_fork do
PublicationJob.register(:tex, SaveJob)
PublicationJob.register(:saved_tex, TexJob)
end
end
This will reset those variables after spring completes the fork and reloading code. I wrap it in a check to ensure that Spring is available, which it likely won't be in production.
We always used to put our application config into the environment files. That's no good for production management, so now we load it through an initializer:
# myinitializer.rb
ApplicationConfig = YAML.load_file("#{Rails.root}/config/application/default.yml").symbolize_keys()
As soon as we started accessing the configuration through ApplicationConfig, application test performance got much worse. One rspec suite dropped from 4 seconds to 30.
In our application controller, we need to take some action using a before_filter, which works as follows:
before_filter :extra_control
def extra_control
if ApplicationConfig.some_flag
...
end
end
Declaring a variable pointing to ApplicationConfig fully restores performance:
config = ApplicationConfig
def extra_control
if config.some_flag
...
end
end
Why? Why does accessing this through the global variable destroy performance? We do this throughout the code base in views and other controllers. Do we need to do this differently, e.g. by injecting an instance variable into all controllers?
Edit: we did verify that the code loading the configuration from YAML gets called exactly once either way, so repeated loading does not seem to be the root cause.
Edit: it turns out that this was a bug caused by a setting variable that was being loaded as a string rather than a boolean, causing the application to go into a test sleep pattern :( Sorry, thanks for trying. 3 days of my life I'll never get back!
I can't speak to why your approach is slow, but maybe a different way of handling settings would be worth looking into. I prefer the idea of having a singleton class that contains config information:
class SiteSettings
def initialize
#settings = {}
end
##instance = SiteSettings.new
def set_setting(key, value)
#settings ||= {}
#settings[key] = value
end
def self.setting(key, value)
##instance.set_setting(key, value)
end
def settings
#settings
end
def self.method_missing(meth, *args, &block)
if ##instance.settings.has_key?(meth)
##instance.settings[meth]
else
super
end
end
setting :test_setting, 'test'
setting :another_test_setting '.....'
end
puts SiteSettings.test_setting
puts SiteSettings.another_test_setting
The Discourse app takes a similar approach.
site_setting.rb
site_setting_extension.rb
Try use const:
APPLICATIONCONFIG = YAML.load(File.read(File.expand_path("#{RAILS_ROOT}/config/application/default.yml", __FILE__)))
or
APPLICATIONCONFIG = YAML::load(File.open("#{RAILS_ROOT}/config/application/default.yml"))
I think its make your file slow because load file but not open/read.
I think it will help to figure out if the YAML file load\read is slow, or YAML parsing is slow. My guess is that ruby is parsing the YAML file every time you call your ApplicationConfig variable. Can you try renaming it to APPLICATION_CONFIG like so:
APPLICATION_CONFIG = YAML.load(File.read(Rails.root.join('config', 'application', 'my_config.yml')))
Please do not inject an instance variable in all your controllers :)
The other answers here did not make a difference - this was caused by an unrelated problem. For the record
Initializers are called exactly once by rspec
It does not make a difference whether ApplicationConfig or APPLICATION_CONFIG is used
Assigning to a variable did not make a speed difference either
I have rails 3 app that generates a lot of requests for analytics. Unfortunately this drowns the logs and I lose the main page requests that I actually care about. I want to separate these requests in to a separate log file. Is there a way to specify certain actions to go to a certain log file? Or possibly a way to reduce the logging level of these actions, and then only show certain level logs when reading back the log file?
I found this site, which talked about using a middleware for silencing log actions. I used the same sort of idea and ended up writing a middleware that would swap the logger depending on which action was being called. Here is the middleware, which i put in lib/noisy_logger.rb
class NoisyLogger < Rails::Rack::Logger
def initialize app, opts = {}
#default_log = Rails.logger
# Put the noisy log in the same directory as the default log.
#noisy_log = Logger.new Rails.root.join('log', 'noisy.log')
#app = app
#opts = opts
#opts[:noisy] = Array #opts[:noisy]
super app
end
def call env
if #opts[:noisy].include? env['PATH_INFO']
logfile = #noisy_log
else
logfile = #default_log
end
# What?! Why are these all separate?
ActiveRecord::Base.logger = logfile
ActionController::Base.logger = logfile
Rails.logger = logfile
# The Rails::Rack::Logger class is responsible for logging the
# 'starting GET blah blah' log line. We need to call super here (as opposed
# to #app.call) to make sure that line gets output. However, the
# ActiveSupport::LogSubscriber class (which Rails::Rack::Logger inherits
# from) caches the logger, so we have to override that too
#logger = logfile
super
end
end
And then this goes in config/initializers/noisy_log.rb
MyApp::Application.config.middleware.swap(
Rails::Rack::Logger, NoisyLogger, :noisy => "/analytics/track"
)
Hope that helps someone!
One option could be using a service like New Relic which would give you the required scoping (per action).
I have an Engine which is extending another Engine's classes in its initializers like so:
module MyApp
class Engine < ::Rails::Engine
initializer 'extend Product' do
AnotherApp::Product.send :include, MyApp::ProductExtender
end
end
end
The ProductExtendermodule calls some methods on the AnotherApp::Product when it is included, e.g.
module ProductExtender
def self.included( model )
model.send :include, MethodsToCall
end
module MethodsToCall
def self.included( m )
m.has_many :variations
end
end
end
This works in test and production environments, but when config.cache_classes = false, it throws a NoMethodError at me when I try to call something defined by the ProductExtender, like #product.variations.
Needless to say, it is chilling to see all my tests pass and then get slammed with an error in development. It doesn't happen when I set cache_classes = true, but it makes me wonder if I'm doing something I shouldn't be.
My question is twofold: Why is this happening, and is there a better way I should be achieving this functionality of extending/calling methods on another application's object?
Thanks all!
I managed to solve this problem using a to_prepare block instead of the initializer. The to_prepare block executes once in production and before each request in development, so seems to meet our needs.
It wasn't obvious when I was researching Rails::Engine since it is inherited from Rails::Railtie::Configuration.
So instead of the code in the question, I would have:
module MyApp
class Engine < ::Rails::Engine
config.to_prepare do
AnotherApp::Product.send :include, MyApp::ProductExtender
end
end
end
cache_classes has actually a misleading name: There is no caching involved. If you set this option to false, rails explicitly unloads your application code and reloads it when needed. This enables for changes you make in development to have an effect without having to restart the (server) process.
In your case, AnotherApp::Product is reloaded, as is ProductExtender, but the initializer is not fired again, after the reload, so AnotherApp::Product is not 'extended'.
I know this problem very well and ended up running my development environment with cache_classes = true and occasionally restart my server. I had not so much development to do on engines/plugins, so this was the easiest way.