How do I collate log files from different processes? - ruby

I have a application written in Ruby which generates a log on STDOUT. The application would be running in multiple processes but I need to collate logs generated from each of the process into a single file. Is it possible?
I read somewhere that Syslog can be used for this but I am not sure how it can be used.

You don't say what OS you need this to work on, but for Linux and Mac OS, Ruby's syslog is a good candidate. I don't know if it's implemented on Windows, but I don't think it was.
The built-in documentation is not very good, but if you look at the source or "Ruby Syslog README" you'll get a good idea how to use it.
In the past I used the following code. For your purposes you'd want to reroute your output from STDOUT to this syslog method.
require 'syslog'
def syslog(msg, level=:info)
# if msg is an array, we assume it is a cmd, message pair.
if (msg.is_a?(Array))
msg = msg.join(' => ')
end
# escape all '%' using '%%'
msg.gsub!('%', '%%')
Syslog.open($0) { |s|
case level
when :crit, :critical
s.notice(msg)
when :emerg, :emergency
s.emerg(msg)
when :alert
s.alert(msg)
when :err, :error
s.err(msg)
when :warn, :warning
s.warning(msg)
when :notice
s.notice(msg)
when :info, :information
s.info(msg)
when :debug, :debugging
s.debug(msg)
end
}
end
Check the syslog man pages for information about the various logging levels.
Syslog.open($0) tells syslog to use the entire path to your application when inserting records. You might want to shrink $0 to just the application name by using Syslog.open(File.basename($0)).

I finally decided to use log4r. It can collate logs from different processes and add them to the same log file.

Related

Asking user for information, and never having to ask again

I want to ask for user input, but I only want to do it once (possibly save the information within the program), meaning, something like this:
print "Enter your name (you will only need to do this once): "
name = gets.chomp
str = "Hello there #{name}" #<= As long as the user has put their name in the very first
# time the program was run, I want them to never have to put thier name in again
How can I got about doing this within a Ruby program?
This program will be run by multiple users throughout the day on multiple systems. I've attempted to store it into memory, but obviously that failed because from my understand that memory is wiped everytime a Ruby program stops executing.
My attempts:
def capture_user
print 'Enter your name: '
name = gets.chomp
end
#<= works but user has to put in name multiple times
def capture_name
if File.read('name.txt') == ''
print "\e[36mEnter name to appear on email (you will only have to do this once):\e[0m "
#esd_user = gets.chomp
File.open('name.txt', 'w') { |s| s.puts(#esd_user) }
else
#esd_user = File.read('name.txt')
end
end
#<= works but there has to be a better way to do this?
require 'tempfile'
def capture_name
file = Tempfile.new('user')
if File.read(file) == ''
print "\e[36mEnter name to appear on email (you will only have to do this once):\e[0m "
#esd_user = gets.chomp
File.open(file, 'w') { |s| s.puts(#esd_user) }
else
#esd_user = File.read(file)
end
end
#<= Also used a tempfile, this is a little bit over kill I think,
# and doesn't really help because the users can't access their Appdata
You will want to store the username in a file on the local file system. Ruby provides many ways to do this, and we'll explore one in this answer: YAML files.
YAML files are a structured storage file that can store all kinds of different data, and is a good place to store config data. In fact, YAML configuration files are key parts of the largest Ruby projects in existence. YAML gives you a good starting point for supporting future configuration needs, beyond the current one, which is a great way to plan feature development.
So, how does it work? Let's take a look at your requirement using a YAML config:
require 'yaml'
config_filename = "config.yml"
config = {}
name = nil
if file_exists(config_filename)
begin
config = YAML.load_file(config_filename)
name = config["name"]
rescue ArgumentError => e
puts "Unable to parse the YAML config file."
puts "Would you like to proceed?"
proceed = gets.chomp
# Allow the user to type things like "N", "n", "No", "nay", "nyet", etc to abort
if proceed.length > 0 && proceed[0].upcase == "N"
abort "User chose not to proceed. Aborting!"
end
end
end
if name.nil? || (name.strip.length == 0)
print "Enter your name (you will only need to do this once): "
name = gets.chomp
# Store the name in the config (in memory)
config["name"] = name
# Convert config hash to a YAML config string
yaml_string = config.to_yaml
# Save the YAML config string to the config file
File.open(config_filename, "w") do |out|
YAML.dump(config, out)
end
end
Rather than show you the bare minimum to meet your needs, this code includes a little error handling and some simple safety checks on the config file. It may well be robust enough for you to use immediately.
The very first bit simply requires the YAML standard library. This makes the YAML functions work in your program. If you have a loader file or some other common mechanism like that, simply place the require 'yaml' there.
After that, we initialize some variables that get used in this process. You should note that the config_filename has no path information in it, so it will be read from the current directory. You will likely want to store the config file in a common place, such as in ~/.my-program-name/config.yml or C:\Documents and Settings\MyUserName\Application Data\MyProgramName\. This can be done pretty easily, and there's plenty to help, such as this Location to Put User Config Files in Windows and Location of ini/config files in linux/unix.
Next, we check to see if the file actually exists, and if so, we attempt to read the YAML contents from it. The YAML.load_file() method handles all the heavy lifting here, so you just have to ask the config hash that's returned for the key that you're interested in, in this case, the "name" key.
If an error occurs while reading the YAML file, it indicates that the file might possibly be corrupted, so we try to deal with that. YAML files are easy to edit by hand, but when you do that, you can also easily introduce an error that will make loading the YAML file fail. The error handling code here will allow the user to abort the program and go back to fix the YAML file, so that it doesn't simply get overwritten.
After that, we try to see if we've been had a valid name from the YAML config, and if not, we go ahead and accept it from the user. Once they've entered a name, we add it to the config hash, convert the hash to a YAML-formatted string, and then write that string to the config file.
And that's all it takes. Just about anything that you can store in a Ruby hash, you can store in a YAML file. That's a lot of power for storing config information, and if you later need to add more config options, you have a versatile container that you can use exactly for that purpose.
If you want to do any further reading on YAML, you can find some good information here:
YAML in Ruby Tutorial on Robot Has No Heart
Jamming with Ruby YAML on Juixe Techknow
YAML on Struggling with Ruby
While some of these articles are a bit older, they're still very relevant and will give you a jumping off point for further reading. Enjoy!
If you need the name to persist across the user running the script several times, you're going to need to use some sort of data store. As much as I hate flat files, if all you're storing is the user's name, I think this is a valid option.
if File.exist?('username.txt')
name = File.open( 'username.txt', 'r' ) do |file|
name = file.gets
end
else
print "Enter your name (you will only need to do this once): "
name = gets.chomp
File.open( 'username.txt', 'w' ) do |file|
file.puts name
end
end
str = "Hello there #{name}"

Darwin Streaming Server install problems os x

My problem is the same as the one mentioned in this answer. I've been trying to understand the code and this is what I learned:
It is failing in the file parse_xml.cgi, tries to get messages (return $message{$name}) from a file named messages (located in the html_en directory).
The $messages value comes from the method GetMessageHash in file adminprotocol-lib.pl:
sub GetMessageHash
{
return $ENV{"QTSSADMINSERVER_EN_MESSAGEHASH"}
}
The $ENV{"QTSSADMINSERVER_EN_MESSAGEHASH"} is set in the file streamingadminserver.pl:
$ENV{"QTSSADMINSERVER_EN_MESSAGEHASH"} = $messages{"en"}
I dont know anything about Perl so I have no idea of what the problem can be, for what I saw $messages{"en"} has the correct value (if I do print($messages{"en"}{'SunStr'} I get the value "Sun")).
However, if I try to do print($ENV{"QTSSADMINSERVER_EN_MESSAGEHASH"}{'SunStr'} I get nothing. Seems like $ENV{"QTSSADMINSERVER_EN_MESSAGEHASH"} is not set
I tried this simple example and it worked fine:
$ENV{"HELLO"} = "hello";
print($ENV{"HELLO"});
and it works fine, prints "hello".
Any idea of what the problem can be?
Looks like $messages{"en"} is a HashRef: A pointer to some memory address holding a key-value-store. You could even print the associated memory address:
perl -le 'my $hashref = {}; print $hashref;'
HASH(0x1548e78)
0x1548e78 is the address, but it's only valid within the same running process. Re-run the sample command and you'll get different addresses each time.
HASH(0x1548e78) is also just a human-readable representation of the real stored value. Setting $hashref2="HASH(0x1548e78)"; won't create a real reference, just a copy of the human-readable string.
You could easily proof this theory using print $ENV{"QTSSADMINSERVER_EN_MESSAGEHASH"} in both script.
Data::Dumper is typically used to show the contents of the referenced hash (memory location):
use Data::Dumper;
print Dumper($messages{"en"});
# or
print Dumper($ENV{"QTSSADMINSERVER_EN_MESSAGEHASH"});
This will also show if the pointer/reference could be dereferenced in both scripts.
The solution for your problem is probably passing the value instead of the HashRef:
$ENV{"QTSSADMINSERVER_EN_SUN"} = $messages{"en"}->{SunStr};
Best Practice is using a -> between both keys. The " or ' quotes for the key also optional if the key is a plain word.
But passing everything through environment variables feels wrong. They might not be able to hold references on OSX (I don't know). You might want to extract the string storage to a include file and load it via require.
See http://www.perlmaven.com/ or http://learn.perl.org for more about Perl.
fix code:
$$ENV{"QTSSADMINSERVER_EN_MESSAGEHASH"} = $messages{"en"};
sub GetMessageHash
{
return $$ENV{"QTSSADMINSERVER_EN_MESSAGEHASH"};
}
ref:
https://github.com/guangbin79/dss6.0.3-linux-patch

What is the best way to get keyboard events (input without press 'enter') in a Ruby console application?

I've been looking for this answer in the internet for a while and have found other people asking the same thing, even here. So this post will be a presentation of my case and a response to the "solutions" that I have found.
I am such new in Ruby, but for learning purposes I decided to create a gem, here.
I am trying to implement a keyboard navigation to this program, that will allow the user use short-cuts to select what kind of request he want to see. And in the future, arrow navigations, etc.
My problem: I can't find a consistent way to get the keyboard events from the user's console with Ruby.
Solutions that I have tried:
Highline gem: Seems do not support this feature anymore. Anyway it uses the STDIN, keep reading.
STDIN.getch: I need to run it in a parallel loop, because at the same time that the user can use a short-cut, more data can be created and the program needs to show it. And well, I display formated text in the console, (Rails log). When this loop is running, my text lost the all the format.
Curses: Cool but I need to set position(x,y) to display my text every time? It will get confusing.
Here is where I am trying to do it.
You may note that I am using "stty -raw echo" (turns raw off) before show my text and "stty raw -echo" (turns raw on) after. That keeps my text formated.
But my key listener loop is not working. I mean, It works in sometimes but is not consistent. If a press a key twice it don't work anymore and sometimes it stops alone too.
Let me put one part of the code here:
def run
# Two loops run in parallel using Threads.
# stream_log loops like a normal stream in the file, but it also parser the text.
# break it into requests and store in #requests_queue.
# stream_parsed_log stream inside the #requests_queue and shows it in the screen.
#requests_queue = Queue.new
#all_requests = Array.new
# It's not working yet.
Thread.new { listen_keyboard }
Thread.new { stream_log }
stream_parsed_log
end
def listen_keyboard
# not finished
loop do
char = STDIN.getch
case char
when 'q'
puts "Exiting."
exit
when 'a'
#types_to_show = ['GET', 'POST', 'PUT', 'DELETE', 'ASSET']
requests_to_show = filter_to_show(#all_requests)
command = true
when 'p'
#types_to_show = ['POST']
requests_to_show = filter_to_show(#all_requests)
command = true
end
clear_screen if command
#requests_queue += requests_to_show if command
command = false
end
end
I need a light in my path, what should I do?
That one was my mistake.
It's just a logic error in another part of code that was running in another thread so the ruby don't shows the error by default. I used ruby -d and realized what was wrong. This mistake was messing my keyboard input.
So now it's fixed and I am using STDIN.getch with no problem.
I just turn the raw mode off before show any string. And everything is ok.
You can check here, or in the gem itself.
That's it.

Uploading and parsing text document in Rails

In my application, the user must upload a text document, the contents of which are then parsed by the receiving controller action. I've gotten the document to upload successfully, but I'm having trouble reading its contents.
There are several threads on this issue. I've tried more or less everything recommended on these threads, and I'm still unable to resolve the problem.
Here is my code:
file_data = params[:file]
contents = ""
if file_data.respond_to?(:read)
contents = file_data.read
else
if file_data.respond_to?(:path)
File.open(file_data, 'r').each_line do |line|
elts = line.split
#
#
end
end
end
So here are my problems:
file_data doesn't 'respond_to?' either :read or :path. According to some other threads on the topic, if the uploaded file is less than a certain size, it's interpreted as a string and will respond to :read. Otherwise, it should respond to :path. But in my code, it responds to neither.
If I try to take out the if statements and straight away attempt File.open(file_data, 'r'), I get an error saying that the file wasn't found.
Can someone please help me find out what's wrong?
PS, I'm really sorry that this is a redundant question, but I found the other threads unhelpful.
Are you actually storing the file? Because if you are not, of course it can't be found.
First, find out what you're actually getting for file_data by adding debug output of file_data.inspect. It maybe something you don't expect, especially if form isn't set up correctly (i.e. :multipart => true).
Rails should enclose uploaded file in special object providing uniform interface, so that something as simple as this should work:
file_data.read.each_line do |line|
elts = line.split
#
#
end

Convert a .rtf into a mac .r resource, in a scriptable way

I currently have a SLA in a .rtf format, which is to be integrated into .dmg using the intermediary .r mac resource format, which is used by the Rez utility. I had already done it by hand once, but updates made to the .rtf file are overwhelming to propagate to the disk image, and error-prone. I would like to automate this task, which could also help adding other languages or variants.
How could the process of .rtf to .r text conversion be automated?
Thanks.
Only because I didn't fully understand how the accepted answer actually achieved the goal, I use a combination of a script to generate the hex encoding:
#!/usr/bin/env ruby
# Makes resource (.r) text from binaries.
def usage
puts "usage: #{$0} infile"
puts ""
puts " infile The file to convert (the output will go to stdout)"
exit 1
end
infile = ARGV[0] || usage
data = File.read(infile)
data.bytes.each_slice(16) do |slice|
hex = slice.each_slice(2).map { |pair| pair.pack('C*').unpack('H*')[0] }.join(' ')
# We could put the comments in too, but it probably isn't a big deal.
puts "\t$\"#{hex}\""
end
The output of this is inserted into a variable during the build and then the variable ends up in a template (we're using Ant to do this, but the specifics aren't particularly interesting):
data 'RTF ' (5000, "English SLA") {
#english.licence#
};
The one bit of this which did take quite a while to figure out is that 'RTF ' can be used for the resource directly. The Apple docs say to separately insert 'TEXT' (with just the plain text) and 'styl' (with just the style). There are tools to do this of course, but it was one more tool to run and I could never figure out how to make hyperlinks work in the resulting DMG. With 'RTF ', hyperlinks just work.
Hoping that this saves someone time in the future.
Use the unrtf port (from macports), then format the lines, heading and tail with a shell script.

Resources