Watching/tailing multiple log files at the same time with Ruby’s NET::SSH library
A good testing habit when working with web apps is to monitor the log files of servers as you test. In some cases this is easy, especially where there’s a single application server. With the trend toward more service-oriented architectures, and server clusters for high-traffic applications, the environments I’m working in tend to have many log files spread over multiple servers and folders. My old friend, ‘tail -f error.log’ becomes a bit more difficult.
I’d heard about Ruby’s SSH library quite a while back, but only recently have my spare time and my memory conspired to work together. The library lets you connect to a server via SSH, execute commands and see the result. So today I quickly hacked out a script to let me watch multiple (Linux) log files at the same time. This might work for other environments that support SSH, but I only have it connecting to Linux servers at this time.
Code is below, very hacky, and tailored to my needs. If you have any questions, leave a comment, but hopefully this helps someone get started!
To install, use the instructions for NET::SSH version one at http://net-ssh.rubyforge.org/ssh/v1/index.html. This will actually install version two though, so you’ll need the docs and examples from http://net-ssh.rubyforge.org/sftp/v2/api/index.html
require 'net/ssh' class Logfile def initialize(name,server,filename) @name=name @server=server @filename=filename @current_file_size=0 @ssh_server='server' @username='username' @password={:password=>'password'} end #You'll need to customise this method so that it generates the #full path of the log file you're interested in. This one is for Splunk logs. def full_log_path today=Time.now day=today.day.to_s.rjust(2,'0') month=today.month.to_s.rjust(2,'0') year=today.year.to_s todays_log_folder="#{year}-#{month}-#{day}" full_path="/var/log/#{@server}/#{todays_log_folder}/#{@filename}" return full_path end def get_new_lines Net::SSH.start(@log_server,@username,@password) do |session| new_file_size=session.exec!("wc -l #{full_log_path}").split(" ")[0].to_i lines_to_get=new_file_size-@current_file_size # Don't generally need to get everything if the error logs are being flooded lines_to_get=100 if lines_to_get > 1000 new_logs=session.exec!("tail -n #{lines_to_get.to_s} #{full_log_path}") @current_file_size=new_file_size return new_logs end end end logfiles=[] logfiles.push Logfile.new(:log_name1,"log_app_folder1","log_file_name1") logfiles.push Logfile.new(:log_name2,"log_app_folder2","log_file_name2") 5.times do logfiles.each do | logfile | changes=logfile.get_new_lines puts changes if !changes.nil? $stdout.flush end sleep 10 end