From 9bc8ea9438cc1fcd60ea6dc292d2c5dd052dec8b Mon Sep 17 00:00:00 2001 From: dxhuy1988 Date: Fri, 9 Jan 2015 09:29:08 +0900 Subject: [PATCH] add lotus::logger --- lib/lotus.rb | 1 + lib/lotus/loader.rb | 12 ++++++ lib/lotus/logger.rb | 94 +++++++++++++++++++++++++++++++++++++++++++++ test/loader_test.rb | 14 +++++++ test/logger_test.rb | 73 +++++++++++++++++++++++++++++++++++ test/test_helper.rb | 31 ++++++++++++++- 6 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 lib/lotus/logger.rb create mode 100644 test/logger_test.rb diff --git a/lib/lotus.rb b/lib/lotus.rb index 141f01076..2a0450b54 100644 --- a/lib/lotus.rb +++ b/lib/lotus.rb @@ -1,6 +1,7 @@ require 'lotus/version' require 'lotus/application' require 'lotus/container' +require 'lotus/logger' # A complete web framework for Ruby # diff --git a/lib/lotus/loader.rb b/lib/lotus/loader.rb index 436af243d..60f4f18fb 100644 --- a/lib/lotus/loader.rb +++ b/lib/lotus/loader.rb @@ -2,6 +2,7 @@ require 'lotus/utils/kernel' require 'lotus/utils/string' require 'lotus/routes' +require 'lotus/logger' require 'lotus/routing/default' require 'lotus/action/cookies' require 'lotus/action/session' @@ -40,6 +41,7 @@ def configure_frameworks! _configure_model_framework! if defined?(Lotus::Model) _configure_controller_framework! _configure_view_framework! + _configure_static_variables! end def _configure_controller_framework! @@ -87,6 +89,16 @@ def _configure_model_framework! end end + def _configure_static_variables! + _initialize_logger! + end + + def _initialize_logger! + unless application_module.const_defined?('Logger', false) + logger = Lotus::Logger.new(application_module.to_s) + application_module.const_set('Logger', logger) + end + end def load_frameworks! _load_view_framework! diff --git a/lib/lotus/logger.rb b/lib/lotus/logger.rb new file mode 100644 index 000000000..1ad69dc0c --- /dev/null +++ b/lib/lotus/logger.rb @@ -0,0 +1,94 @@ +require 'logger' +require 'lotus/utils/string' + +module Lotus + # Lotus default logger + # + # Implement with the same interface of ruby std ::Logger + # Opts out the logdev parameter and always use STDOUT as outout device + # + # Lotus logger also has the app tag concept, which used to identify + # which application the log come from. + # + # Lotus Logger default comes with Lotus application + # and uses name of highest module namespace as app_tag + # + # When stands alone, Lotus Logger tries to infer app tag from highest namespace + # When has no namespace, Lotus Logger takes [Shared] as default app tag + # + # @example + # #1 Logger with namespace + # module TestApp + # class AppLogger << Lotus::Logger; end + # def log + # Applogger.new.info('foo') + # #=> output: I, [2015-01-10T21:55:12.727259 #80487] INFO -- [TestApp] : foo + # end + # end + # + # #2 Logger without namespace + # class AppLogger < Lotus::Logger + # end + # Applogger.new.info('foo') + # #=> output: I, [2015-01-10T21:55:12.727259 #80487] INFO -- [Shared] : foo + # + # #3 Logger inside a lotus application + # module LotusModule + # class App < Lotus::Application + # load! + # end + # end + # LotusModule::Logger.info('foo') + # => output: I, [2015-01-10T21:55:12.727259 #80487] INFO -- [LotusModule] : foo + # + # @see ::Logger + # + # @since 0.2.1 + class Logger < ::Logger + + attr_accessor :app_tag + + # Override Ruby's Logger#initialize + # + # @param logdev is default to STDOUT + # + # @since 0.2.1 + def initialize(app_tag=nil, *args) + super(STDOUT, *args) + @app_tag = app_tag + @formatter = Lotus::Logger::Formatter.new.tap { |f| f.app_tag = self.app_tag } + end + + # app_tag is the identification of current app + # app_tag is default to use highest namespace if current namespace + # if app_tag is blank, lotus use default app_tag 'shared' + # @param logdev is default to STDOUT + # + # @since 0.2.1 + def app_tag + @app_tag || _app_tag_from_namespace || _default_app_tag + end + + class Formatter < ::Logger::Formatter + attr_accessor :app_tag + + def call(severity, time, progname, msg) + time = time.utc + progname = "[#{@app_tag}] #{progname}" + super(severity, time, progname, msg) + end + end + + private + def _app_tag_from_namespace + class_name = self.class.name + return nil unless class_name.index('::') + + Utils::String.new(class_name).namespace + end + + def _default_app_tag + 'Shared' + end + end +end diff --git a/test/loader_test.rb b/test/loader_test.rb index 98b3fb909..39267a1c3 100644 --- a/test/loader_test.rb +++ b/test/loader_test.rb @@ -15,6 +15,7 @@ assert defined?(CoffeeShop::Entity), 'expected CoffeeShop::Entity' assert defined?(CoffeeShop::Repository), 'expected CoffeeShop::Repository' assert defined?(CoffeeShop::Presenter), 'expected CoffeeShop::Presenter' + assert defined?(CoffeeShop::Logger), 'expected CoffeeShop::Logger' end it 'generates per application classes' do @@ -74,6 +75,19 @@ @application.middleware.must_be_kind_of(Lotus::Middleware) end end + + describe 'logger' do + it 'has app module name along with log output' do + output = + stub_stdout_constant do + module CocacolaShop + class Application < Lotus::Application; load!; end + end + CocacolaShop::Logger.info 'foo' + end + output.must_match /CocacolaShop/ + end + end end # describe 'finalization' do diff --git a/test/logger_test.rb b/test/logger_test.rb new file mode 100644 index 000000000..5eb052e06 --- /dev/null +++ b/test/logger_test.rb @@ -0,0 +1,73 @@ +require 'test_helper' + +describe Lotus::Logger do + + before do + #clear defined class + Object.send(:remove_const, :TestLogger) if Object.constants.include?(:TestLogger) + end + + it 'like std logger, sets log level to info by default' do + class TestLogger < Lotus::Logger; end + TestLogger.new.info?.must_equal true + end + + it 'always use STDOUT' do + output = + stub_stdout_constant do + class TestLogger < Lotus::Logger; end + logger = TestLogger.new + logger.info('foo') + end + output.must_match /foo/ + end + + it 'has app_tag when log' do + output = + stub_stdout_constant do + module App; class TestLogger < Lotus::Logger; end; end + logger = App::TestLogger.new + logger.info('foo') + end + output.must_match /App/ + end + + it 'has default app tag when not in any namespace' do + class TestLogger < Lotus::Logger; end + TestLogger.new.send(:app_tag).must_equal 'Shared' + end + + it 'infers apptag from namespace' do + module App2 + class TestLogger < Lotus::Logger;end + class Bar + def hoge + TestLogger.new.send(:app_tag).must_equal 'App2' + end + end + end + App2::Bar.new.hoge + end + + it 'uses custom app_tag from override class' do + class TestLogger < Lotus::Logger; def app_tag; 'bar'; end; end + output = + stub_stdout_constant do + TestLogger.new.info('') + end + output.must_match /bar/ + end + + it 'has format "#{Severity}, [%Y-%m-%dT%H:%M:%S.%6N #{Pid}] #{Severity} -- [#{app_tag}] : #{message}\n"' do + format = "%Y-%m-%dT%H:%M:%S.%6N " + stub_time_now do + strtime = Time.now.strftime(format) + output = + stub_stdout_constant do + class TestLogger < Lotus::Logger;end + TestLogger.new.info('foo') + end + output.must_equal "I, [1988-09-01T00:00:00.000000 ##{Process.pid}] INFO -- [Shared] : foo\n" + end + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index d9c04831a..5408a850b 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -105,5 +105,34 @@ def dependencies end DependenciesReporter.new.run -require 'fixtures' +def stub_stdout_constant + begin_block = <<-BLOCK + original_verbosity = $VERBOSE + $VERBOSE = nil + + origin_stdout = STDOUT + STDOUT = StringIO.new + BLOCK + TOPLEVEL_BINDING.eval begin_block + + yield + return_str = STDOUT.string + + ensure_block = <<-BLOCK + STDOUT = origin_stdout + $VERBOSE = original_verbosity + BLOCK + TOPLEVEL_BINDING.eval ensure_block + + return_str +end + + +def stub_time_now + Time.stub :now, Time.utc(1988, 9, 1, 0, 0, 0) do + yield + end +end + +require 'fixtures'