Af - an application framework
gem 'fiksu-af'
This application framework supports:
- an Application class with supporting infrastruction
- command line options integrated into instance (and class) variables
- logging via log4r with support for papertrail
- postgres advisory locking via pg_advisor_locker gem
- postgres database connection updates via pg_application_name gem
- threads and message passing
- application components adding loggers and command line options
Application class
The class ::Af::Application provides a basic interface to the start-up and management of Ruby on Rails scripts.
A simple example of an application which logs its startup and exits is:
class MyApplication < ::Af::Application
opt :word, "the word", :short => :w, :default => "bird"
def work
logger.info "Started up: #{@word} is the word"
exit 0
end
end
Class-method "opt" specifies command line parameter(s) whose arguments are stored in instance variables. MyApplication specifies a single parameter "--word" with a short alias "-w" that accepts a string argument (whose default is "bird", so we know it should be a string). The parameters value will be stored in class instance variable: @word. More information on command line option glue is provided in the Command Line Options section.
The method "logger" is available to ::Af::Application to create and fetch log4r loggers. In this case, we use the basic application logger (named "MyApplication") and logs an INFO level message. More information on the glue ::Af::Application provides with Log4r is in the logging section below.
This application would be run from the command line as:
$ script/rails runner MyApplication.run --word grease
The class method #run provides start-up glue for applications, parsing command line options, environment variables and configuration files. The instance method #work is where application codes start their work.
Command Line Options
a more interesting example of options from multiple classes
class AfSideComponent
include Af::Application::Component
opt_group :side_component_stuff, "options associated with this side component" do
opt :a_side_component_option, :default => "foo"
end
opt :basic_option_from_component, "this is a switch from the side component, in the basic (default) group"
create_proxy_logger :foo
def do_something
foo_logger.info "doing something @@a_side_component_option='#{@@a_side_component_option}'"
foo_logger.info "did something @@basic_option_from_component=#{@@basic_option_from_component.inspect}"
end
end
class AfScriptWithOptions < ::Af::Application
opt_group :singles, "parameters whose types are a single value" do
opt :a_switch, "a switch"
opt :an_int, "an integer of some sort", :default => 1
opt :a_float, "a float of some sort whose default is PI", :default => 3.14
opt :a_string, "a string of some sort", :type => :string
opt :a_uri, "some uri", :type => :uri
opt :a_date, "a date", :type => :date
opt :a_time, "a time", :type => :time
opt :a_datetime, "a datetime", :type => :datetime
opt :a_choice, "a choice", :argument_note => "COLOR", :choices => [:red, :blue, :green, :yellow, :white, :black, :orange]
end
# there is already an advanced option group -- it is set to hidden by default
opt_group :advanced, "advanced parameters", :hidden => false do
opt :a_small_integer, "another integer", :default => 10, :argument_note => "[1 >= INTEGER <= 10]" do |argument,option|
argument = argument.to_i
if (argument < 1 || argument > 10)
opt_error "ERROR: #{option.long_name}: value must be between 1 and 10 (inclusive)"
end
argument
end
opt :a_constrained_number, "a number which is not an integer", :requirement => :required, :argument_note => "NON-INTEGER" do |argument,option|
i_argument = argument.to_i
f_argument = argument.to_f
if (i_argument == f_argument)
opt_error "ERROR: #{option.long_name}: value must not be an integer"
end
f_argument
end
end
opt_group :collections, "parameters whose types are collections" do
opt :a_hash, "a key value pair", :type => :hash
opt :some_ints, "a list of integer", :type => :ints
opt :some_integers, "a list of integer", :type => :integers
opt :some_floats, "a list of floats", :type => :floats
opt :some_numbers, "a list of numbers", :type => :numbers
opt :some_strings, "a list of strings", :type => :strings
opt :some_uris, "a list of uris", :type => :uris
opt :some_dates, "a list of dates", :type => :dates
opt :some_times, "a list of times", :type => :times
opt :some_datetimes, "a list of datetimes", :type => :datetimes
opt :some_choices, "a list of choices", :choices => [:foo, :bar, :baz, :beltch]
end
opt_group :blank, "a blank group that won't be seen"
opt :word, "the word to print", :default => :foo, :choices => [:foo, :bar, :baz, :beltch]
opt :words, "the words to print", :default => [:foo], :choices => [:foo, :bar, :baz, :beltch]
opt :numbers, "the number lists", :default => [1,2,3]
opt :switcher, "this is a switch"
def af_opt_class_path
[AfSideComponent] + super
end
def logger
super('Process::T')
end
def work
logger.info "switcher: #{switcher.inspect}"
logger.info "WORK STARTED: #{@word}"
logger.info @words.inspect
logger.info "NUMBERS: #{@numbers.inspect}"
AfSideComponent.new.do_something
logger.info "WORK COMPLETED: #{@word}"
end
end
Logging with Log4r
Some changes to Log4r:
- root loggers are no longer null loggers -- you can add outputters to them
- the global logger is used to set a global log level (root is no longer used for this)
- yaml configurator has been updated to handle new root logger semantics
- yaml configurator has been updated to accept a set of files AND sections (default section: log4r_config)
What this really means is that you can set up one outputter in the root logger to manage the default logging behavior.
Thread Pool
TODO
TCP Message Passing
TODO