Multiple log file and log rotation in Ruby on Rails - ruby-on-rails

I'm a newbie in ruby on rails. I struggled to find the solution for writing multiple logs and the daily rolling file appender.
The output will be like that: (logfile in folder {my project}/log)
db_access.log
db_access.log.20200513
...
user_operator.log
user_operator.log.20200513
...
And I have found a simple solution.

Add gem 'multi_logger' in Gemfile and run bundle install.
Create file logger.rb in /config/initializers and add below code
MultiLogger.add_logger('user_operation', shift_age: 'daily', shift_period_suffix: '%Y%m%d')
MultiLogger.add_logger('db_access', shift_age: 'daily', shift_period_suffix: '%Y%m%d')
Now, you can write log in your project
Rails.logger.user_operation.info 'your_message'
Rails.logger.db_access.info 'your_message'
(write log in level info)
You can also customize your log by adding other options: logdev, shift_age, shift_size, level, progname, formatter, datetime_format, shift_period_suffix

Related

How to set up ActionCable with Importmaps in Rails 6? (MRI and Jruby)

Actioncable - an overview.
I am using jruby 9.2.16 (hence ruby 2.5.7) with rails 6.1.6.1.
I am not sure if only in development or only without ssl (wss) Actioncable can be used with the simple client side:
var ws = new WebSocket('ws://0.0.0.0:3000/channels');
ws.onmessage = function(e){ console.log(e.data); }
But at least I didn't get it running to do "Streaming from Channel" using wss in production, as it works locally (visible starting 'redis-cli' in terminal, and then 'monitor').
So I tried to implement the actioncable client scripts and hence 8 days got lost.
First I struggled with the fact that there is no description which is somehow complete. Many people publish particular solutions, but this is like gambling: maybe you are lucky.
Second, files are named in a way that seems to be general even though they are only about actioncable (the folder 'javascript', or 'application.js')
It is misleading to not call them 'actioncable_files' and 'actioncable_app.js' and voilĂ  there are problems because of several files with the same name.
The next problem is, that lots has to be changed only, because the orderly structure of files is ignored. Javascripts no longer are in assets, but why?
They could be in assets/javascripts/actioncable/ ?
So manifest.js has to be changed, and even attributes have to be added in application.rb (attr_accessor :importmap). Something you find after some days in a forum.
but even more confusing: importmap is a gem, which requires some directories and which somehow has to be installed (rake app:update:bin, rails importmap:install) and someone wrote, the order of the gems were relevant but you cannot only deinstall actioncable gem, because rails depends on that. Dependencies could be organized using a permutation of priority.
importmaps is not working in firefox, so you need shims additionally
But in the end, everything what importmap does, looks like reinventing the wheel to me: it loads javascript files. Something, that can be done easily manually.
Also importing modules is possible in simple javascript. And what else then a javascript file shall it be, that the browser finally will work with?
Now importmap creates from a string '#rails/actioncable' another string 'actioncable.esm.js', which in my case (after 8 days of working 15 h to get it to work, still is not found automatically.
I cannot find that file, or any description where it is generated, or if it is only a link, or has to be compiled somehwere, but it looks to me, as if importmap is completely redundant and only makes things very complicated. I don't understand the benefit from writing a string 'xyz' which is translated in a laborious way to set up into another string 'xyz_2', which also might not be found. And if there are variables, they can be loaded directly using the same idea like action_cable_meta_tag.
Technically Actioncable is only doing what Faye did before. So why do we need all that so called "modern" way, which I think only is reinventing the wheel?
So, I would like to create a description on how to install actioncable in an easy way - without unnecessary tools and clearly.
But first I need to get it to work on my own. And therefore the question is: what shall I do due to:
GET http://0.0.0.0:3000/actioncable.esm.js net::ERR_ABORTED 404 (Not Found)
Thanks everyone for any idea!
I've never set up ActionCable before and never used it (except second hand through Turbo::Broadcastable). Seems like you had quite the journey. I used rails guides and rails github to set it up.
First of all, from importmap-rails:
Note: In order to use JavaScript from Rails frameworks like Action Cable, Action Text, and Active Storage, you must be running Rails 7.0+. This was the first version that shipped with ESM compatible builds of these libraries.
https://github.com/rails/importmap-rails#installation
Challenge accepted. I'll use mri for now (I'll try with your versions later, to see if anything weird comes up).
ActionCable
$ rails _6.1.6.1_ new cable --skip-javascript
$ cd cable
# https://github.com/rails/importmap-rails#installation
$ bin/bundle add importmap-rails
$ bin/rails importmap:install
# ActionCable guide seems rather crusty. Until this section:
# https://guides.rubyonrails.org/v6.1/action_cable_overview.html#connect-consumer
# A generator for the client side js is mentioned in the code comment.
$ bin/rails g channel chat
# Oops, generated server side rb as well.
# This should really be at the start of the guide.
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
# NOTE: just keep it simple
stream_from "some_channel"
end
end
The directory app/javascript seems generic, because it is. This is for all Javascript stuff, used by shakapacker, jsbundling-rails, importmap-rails and others. I've described it a bit here: https://stackoverflow.com/a/73174481/207090
// app/javascript/channels/chat_channel.js
import consumer from "./consumer"
consumer.subscriptions.create("ChatChannel", {
connected() {
// NOTE: We have to check if our set up is online first,
// before chatting and authenticating or anything else.
console.log("ChatChannel connected")
},
disconnected() {},
received(data) {}
});
To broadcast a message, call this somewhere in your app [sic]:
ActionCable.server.broadcast("some_channel", "some message")
Ok, we have to make a controller anyway:
$ bin/rails g scaffold Message content
$ bin/rails db:migrate
$ open http://localhost:3000/messages
$ bin/rails s
Also, channels have to be imported somewhere, to load on the page. javascript_importmap_tags in the layout only imports application:
https://github.com/rails/importmap-rails#usage
<script type="module">import "application"</script>
<!-- ^ -->
<!-- this imports the pinned `application` -->
Makes sense to import channels in application.js. Can't import ./channels/index because it has require. We'd have to use node for it to work or do something else to import all the channels. Manual way is the simplest:
// app/javascript/channels/index.js
// NOTE: it works a little differently with importmaps that I haven't mentioned yet.
// skip this index file for now, and import channels in application.js
// app/javascript/application.js
import "./channels/chat_channel"
Browser console shows missing #rails/actioncable. Nobody told me to install it yet. Use pin command to add it:
https://github.com/rails/importmap-rails#using-npm-packages-via-javascript-cdns
$ bin/importmap pin #rails/actioncable
Refresh the browser:
ChatChannel connected chat_channel:5
We got our javascript on the page. Let's make it broadcast:
# app/controllers/messages_controller.rb
# POST /messages
def create
#message = Message.create(message_params)
ActionCable.server.broadcast("some_channel", #message.content)
end
<!-- app/views/messages/index.html.erb -->
<div id="chat"></div> <!-- output for broadcasted messages -->
<!-- since I have no rails ujs, for my purposes: bushcraft { remote: true } -->
<!-- v -->
<%= form_with model: Message.new, html: { onsubmit: "remote(event, this)" } do |f| %>
<%= f.text_field :content %>
<%= f.submit %>
<% end %>
<script type="text/javascript">
// you can skip this, I assume you have `rails_ujs` installed or `turbo`.
// { remote: true } or { local: false } is all you need on the form.
function remote(e, form) {
e.preventDefault();
fetch(form.action, {method: form.method, body: new FormData(form)})
form["message[content]"].value = ""
}
</script>
We know we're connected. The form is submitting to MessagesController#create without refreshing where we're broadcasting to "some_channel". All that's left is to do is output data on the page:
https://guides.rubyonrails.org/v6.1/action_cable_overview.html#client-server-interactions-subscriptions
// app/javascript/channels/chat_channel.js
// update received() function
received(data) {
document.querySelector("#chat")
.insertAdjacentHTML("beforeend", `<p>${data}</p>`)
}
ActionCable done. Now let's fix importmaps.
Importmaps
Something I didn't mention before and it is super important to understand.
Everything works, but, only in development, I explained why here:
https://stackoverflow.com/a/73136675/207090
URLs and relative or absolute paths will not be mapped and more importantly will bypass the asset pipeline sprockets. To actually use importmaps, all the files in app/javascript/channels have to be mapped, aka pinned, and then referred to only by the pinned name when importing.
# config/importmap.rb
# NOTE: luckily there is a command to help with bulk pins
pin_all_from "app/javascript/channels", under: "channels"
pin "application", preload: true
pin "#rails/actioncable", to: "https://ga.jspm.io/npm:#rails/actioncable#7.0.3-1/app/assets/javascripts/actioncable.esm.js"
# NOTE: the big reveal -> follow me ->------------------------------------------------------------------^^^^^^^^^^^^^^^^^^
# NOTE: this only works in rails 7+
# pin "#rails/actioncable", to: "actioncable.esm.js"
# `actioncable.esm.js` is in the asset pipeline so to speak and can be found here:
# https://github.com/rails/rails/tree/v7.0.3.1/actioncable/app/assets/javascripts
For some info on pin and pin_all_from:
https://stackoverflow.com/a/72855705/207090
You can see the importmaps this creates in the browser or in the terminal:
$ bin/importmap json
{
"imports": {
"application": "/assets/application-3ac17ae8a9bbfcdc9571d7ffac88746f5a76b18c149fdaf02fa7ed721b3e7c49.js",
"#rails/actioncable": "https://ga.jspm.io/npm:#rails/actioncable#7.0.3-1/app/assets/javascripts/actioncable.esm.js",
"channels": "/assets/channels/index-78e712d4a980790be34a2e859a2bd9a1121f9f3b508bd3f7de89889ff75828a0.js",
"channels/chat_channel": "/assets/channels/chat_channel-0a2f983da2629a4d7edef5b7f05a494670df3f99ec6a22a2e2fee91a5d1c1d05.js",
"channels/consumer": "/assets/channels/consumer-b0ce945e7ae055dba9cceb062a47080dd9c7794a600762c19d38dbde3ba8ff0d.js"
}# ^ ^
} # | |
# names you use urls browser uses
# | to import ^ to actually get it
# | |
# `---> importmaped to ----'
For importmaps info (not the importmap-rails gem):
https://github.com/WICG/import-maps
Importmaps do not import anything, they map name to url. If you make name look like url with /name, ./name, ../name, http://js.cdn/name there is nothing to map.
import "channels/chat_channel"
// stays unchanged and is now the same as
import "/assets/channels/chat_channel-0a2f983da2629a4d7edef5b7f05a494670df3f99ec6a22a2e2fee91a5d1c1d05.js"
// because we have an importmap for "channels/chat_channel"
You don't want to use the second form with an absolute path in you js files, because the digest hash changes on file updates to invalidate the browser cache (this is handled by sprockets).
Convert all the imports:
import consumer from "./consumer"
import "./channels/chat_channel"
to match the pinned names:
import consumer from "channels/consumer"
import "channels/chat_channel"
// import "channels" // is mapped to `channels/index`
// TODO: want to auto import channels in index file?
// just get all the pins named *_channel and import them,
// like stumulus-loading does for controllers:
// https://github.com/hotwired/stimulus-rails/blob/v1.1.0/app/assets/javascripts/stimulus-loading.js#L8
Jruby
Same setup on jruby. I just installed it and updated my Gemfile:
# Gemfile
ruby "2.5.7", engine: "jruby", engine_version: "9.2.16.0"
gem "activerecord-jdbcsqlite3-adapter"
gem "importmap-rails", "< 0.8" # after version 0.8.0 ruby >= 2.7 is required
First error, when starting the server:
NoMethodError: private method `importmap=' called for #<Cable::Application:0x496a31da>
importmap= method is defined here:
https://github.com/rails/importmap-rails/blob/v0.7.6/lib/importmap/engine.rb#L4
Rails::Application.send(:attr_accessor, :importmap)
In jruby it defines private methods when used this way:
>> A = Class.new
>> A.attr_accessor(:m)
>> A.new.m
NoMethodError (private method 'm' called for #<A:0x5f2f577>)
The fix is to override the definition in you app, or make those methods public:
# config/application.rb
module Cable
class Application < Rails::Application
# make them public
public :importmap, :importmap=
config.load_defaults 6.1
end
end
That's it. No other issues. You should expect some set backs anyway, because you're using jruby which is quite behind the mri. Ruby 2.5 EOL'd on Apr 05, 2021. You can't expect latest gems to play nice with old ruby versions.

Rails PDFKit - Errno::ENOENT (No such file or directory) when using to_file

Whenever I try to generate a pdf using to_file, the process will just hang, and when I stop the development server I get Errno::ENOENT (No such file or directory - path/to/pdf). However, I am able to render a pdf inline using to_pdf. I'm also able to generate PDFs from the command line in the same folder that I'm trying to generate them in with to_file.
I'm using Rails 3.2.12 and pdfkit 0.8.2. I've tried using wkhtmltopdf versions 0.9.6 through 0.12.4. I'm on Ubuntu 14.04.
Example from controller:
html = render_to_string(:action => "show.html.erb", :formats => :html)
kit.stylesheets << "{Rails.root}/app/assets/stylesheets/stylesheet1.css"
kit.stylesheets << "#{Rails.root}/vendor/assets/stylesheets/stylesheet2.css"
kit.to_file("#{Rails.root}/folder_to_write_to/generated_pdf.pdf")
Turns out the issue was the asset pipeline conflicting with wkhtmltopdf. Added config.threadsafe! to development.rb and it started working.
Another issue can be the default options passed. For example, when I left the default print_media_type option in place, found this message in the log:
The switch --print-media-type, is not support using unpatched qt, and will be ignored."
Only when I override that does it work for me, either in the initializer or like so:
PDFKit.new(html, {print_media_type: false})
The message says it'll be ignored, but it wasn't. It was causing the file to not get generated.

Setting a default editor in Pry

I'm asking about setting a default editor for Pry to use. I'm working on a Rails
app. I created a file named ".pryrc" immediately inside my working directory.
In this file, I wrote this line of code (based on what I read on Github :
Pry.config.editor = proc { |file, line| "sublime +#{line} #{file}" }
This doesn't seem to work. when I try the command ".sublime company.rb", I 'd get
this error:
Error: there was a problem executing system command: sublime company.rb
Can someone tell me what I'm doing wrong please?
Change the configuration to:
Pry.config.editor = proc { |file, line| "sublime #{file}:#{line}" }
You start the editor in Pry by using the edit command.
For example to open test.rb at line 30 use:
edit test.rb:30
See here for more details
For those who have the same problem as mine.Perhaps, you have trouble launching the editor even outside Pry. First thing, make sure to check if the sublime command exists in ur PATH. if not, you probably need to create a symlink between the command and the corresponding path to your app within /usr/local/bin. For more information, see here.

Attach a pdf in asset pipeline using ActionMailer Rails 4

I'm trying to attach a file to an email. The file is in assets/downloads/product.pdf
In the mailer, I have:
attachments["product.pdf"] = File.read(ActionController::Base.helpers.asset_path("product.pdf"))
I've tried:
attachments["product.pdf"] = File.read(ActionController::Base.helpers.asset_url("product.pdf"))
...and even:
attachments["product.pdf"] = File.read(ActionController::Base.helpers.compute_asset_host("product.pdf") + ActionController::Base.helpers.compute_asset_path("product.pdf"))
I always get the same error:
EmailJob crashed!
Errno::ENOENT: No such file or directory - //localhost:3000/assets/product.pdf
...or a variation on the theme. But even when I try using asset_url in the view or just put the url in the browser it works:
http://localhost:3000/assets/product.pdf
I've also tried using straight up:
File.read("app/assets/downloads/product.pdf")
File.read("downloads/product.pdf")
...which works in dev environment but not on staging server (heroku). Error is still:
Errno::ENOENT: No such file or directory - downloads/product-market-fit-storyboard.pdf
Also tried:
File.read("/downloads/product.pdf")
File.read("http://lvh.me:3000/assets/product.pdf")
...don't work at all.
Ideas?
you should use syntex like this.it is work for me may be will work for you also.
File.open(Dir.glob("#{Rails.root}/app/assets/downloads/product.pdf"), "r")
When using mailer, you shouldn't use assets pipline. Asset pipeline would be useful if you wanted to have link to a file inside your email. When rendering an email, action mailer has access to files in app directory.
Please read about attachments in action mailer guide. As you can see, you just need to pass path to a file, not url:
attachments['filename.jpg'] = File.read('/path/to/filename.jpg')

how to delete rails log file after certain size

I have a daemon that runs constantly which fills up the log file(development.log or production.log) pretty quickly. What is the best way to delete the log file after certain size or delete the portion before certain day.
config.logger = Logger.new(config.log_path, 50, 1.megabyte)
but beware that multiple mongrels can have issues with this.
The best way is to set up log rotation, but how you do this is very platform dependent,
so you should add a comment about what you're using, both for development and production.
For our apps running on Linux, we have a file /etc/logrotate.d/appname for each app,
that looks something like this:
/path/to/rails_root_for_app/log/production.log {
daily
missingok
rotate 7
compress
delaycompress
notifempty
create 640 capistrano capistrano
}
This will move the log into a new file once a day, keeping a compressed backup file for each
of the last 7 days.
If you just want to empty the file without keeping any of the data in it while the daemon is
running, simply do this from a shell:
> /path/to/rails_root_for_app/log/development.log
This will truncate the file to 0 bytes length.
I prefer a monthly log file in my production.rb file
config.logger = Logger.new(config.log_path, 'monthly')
Or even better, if all your environments are on either Mac or Linux, and have /usr/sbin/rotatelogs, just use that. It's much more flexible, and doesn't have the data loss issue that logrotate has (even if you use copytruncate).
Add this inside config/application.rb (or just config/environments/production.rb if you only want rotation in prod):
log_pipe = IO.popen("/usr/sbin/rotatelogs #{Rails.root}/log/#{Rails.env}.%Y%m%d.log 86400", 'a')
config.logger = Logger.new(log_pipe)
(From this blog post)
Or you can delegate logging to syslog

Resources