lumberjack-1.0.13/0000755000004100000410000000000013260145344013745 5ustar www-datawww-datalumberjack-1.0.13/Rakefile0000644000004100000410000000155213260145344015415 0ustar www-datawww-datarequire 'rubygems' require 'rake' require 'rubygems/package_task' desc 'Default: run unit tests.' task :default => :test desc 'RVM likes to call it tests' task :tests => :test begin require 'rspec' require 'rspec/core/rake_task' desc 'Run the unit tests' RSpec::Core::RakeTask.new(:test) rescue LoadError task :test do STDERR.puts "You must have rspec 2.0 installed to run the tests" end end namespace :rbx do desc "Cleanup *.rbc files in lib directory" task :delete_rbc_files do FileList["**/*.rbc"].each do |rbc_file| File.delete(rbc_file) end nil end end spec_file = File.expand_path('../lumberjack.gemspec', __FILE__) if File.exist?(spec_file) spec = eval(File.read(spec_file)) Gem::PackageTask.new(spec) do |p| p.gem_spec = spec end Rake.application["package"].prerequisites.unshift("rbx:delete_rbc_files") end lumberjack-1.0.13/MIT_LICENSE0000644000004100000410000000204013260145344015457 0ustar www-datawww-dataCopyright (c) 2011 Brian Durand Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. lumberjack-1.0.13/spec/0000755000004100000410000000000013260145344014677 5ustar www-datawww-datalumberjack-1.0.13/spec/formatter/0000755000004100000410000000000013260145344016702 5ustar www-datawww-datalumberjack-1.0.13/spec/formatter/exception_formatter_spec.rb0000644000004100000410000000127013260145344024322 0ustar www-datawww-datarequire 'spec_helper' describe Lumberjack::Formatter::ExceptionFormatter do it "should convert an exception without a backtrace to a string" do e = ArgumentError.new("not expected") formatter = Lumberjack::Formatter::ExceptionFormatter.new formatter.call(e).should == "ArgumentError: not expected" end it "should convert an exception with a backtrace to a string" do begin raise ArgumentError.new("not expected") rescue => e formatter = Lumberjack::Formatter::ExceptionFormatter.new formatter.call(e).should == "ArgumentError: not expected#{Lumberjack::LINE_SEPARATOR} #{e.backtrace.join(Lumberjack::LINE_SEPARATOR + ' ')}" end end end lumberjack-1.0.13/spec/formatter/string_formatter_spec.rb0000644000004100000410000000052113260145344023630 0ustar www-datawww-datarequire 'spec_helper' describe Lumberjack::Formatter::StringFormatter do it "should format objects as string by calling their to_s method" do formatter = Lumberjack::Formatter::StringFormatter.new formatter.call("abc").should == "abc" formatter.call(:test).should == "test" formatter.call(1).should == "1" end end lumberjack-1.0.13/spec/formatter/pretty_print_formatter_spec.rb0000644000004100000410000000053613260145344025073 0ustar www-datawww-datarequire 'spec_helper' describe Lumberjack::Formatter::PrettyPrintFormatter do it "should convert an object to a string using pretty print" do object = Object.new def object.pretty_print(q) q.text "woot!" end formatter = Lumberjack::Formatter::PrettyPrintFormatter.new formatter.call(object).should == "woot!" end end lumberjack-1.0.13/spec/formatter/inspect_formatter_spec.rb0000644000004100000410000000063313260145344023773 0ustar www-datawww-datarequire 'spec_helper' describe Lumberjack::Formatter::InspectFormatter do it "should format objects as string by calling their inspect method" do formatter = Lumberjack::Formatter::InspectFormatter.new formatter.call("abc").should == "\"abc\"" formatter.call(:test).should == ":test" formatter.call(1).should == "1" formatter.call([:a, 1, "b"]).should == [:a, 1, "b"].inspect end end lumberjack-1.0.13/spec/spec_helper.rb0000644000004100000410000000114113260145344017512 0ustar www-datawww-datarequire File.expand_path("../../lib/lumberjack.rb", __FILE__) require 'stringio' require 'fileutils' require 'timecop' RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = [:should, :expect] end config.mock_with :rspec do |c| c.syntax = [:should, :expect] end end def tmp_dir File.expand_path("../tmp", __FILE__) end def create_tmp_dir FileUtils.rm_r(tmp_dir) if File.exist?(tmp_dir) FileUtils.mkdir_p(tmp_dir) end def delete_tmp_dir FileUtils.rm_r(tmp_dir) end def delete_tmp_files Dir.glob(File.join(tmp_dir, "*")) do |file| File.delete(file) end end lumberjack-1.0.13/spec/log_entry_spec.rb0000644000004100000410000000563413260145344020250 0ustar www-datawww-datarequire 'spec_helper' describe Lumberjack::LogEntry do it "should have a time" do t = Time.now entry = Lumberjack::LogEntry.new(t, Lumberjack::Severity::INFO, "test", "app", 1500, "ABCD") entry.time.should == t entry.time = t + 1 entry.time.should == t + 1 end it "should have a severity" do entry = Lumberjack::LogEntry.new(Time.now, Lumberjack::Severity::INFO, "test", "app", 1500, "ABCD") entry.severity.should == Lumberjack::Severity::INFO entry.severity = Lumberjack::Severity::WARN entry.severity.should == Lumberjack::Severity::WARN end it "should convert a severity label to a numeric level" do entry = Lumberjack::LogEntry.new(Time.now, "INFO", "test", "app", 1500, "ABCD") entry.severity.should == Lumberjack::Severity::INFO end it "should get the severity as a string" do Lumberjack::LogEntry.new(Time.now, Lumberjack::Severity::DEBUG, "test", "app", 1500, nil).severity_label.should == "DEBUG" Lumberjack::LogEntry.new(Time.now, Lumberjack::Severity::INFO, "test", "app", 1500, nil).severity_label.should == "INFO" Lumberjack::LogEntry.new(Time.now, Lumberjack::Severity::WARN, "test", "app", 1500, nil).severity_label.should == "WARN" Lumberjack::LogEntry.new(Time.now, Lumberjack::Severity::ERROR, "test", "app", 1500, nil).severity_label.should == "ERROR" Lumberjack::LogEntry.new(Time.now, Lumberjack::Severity::FATAL, "test", "app", 1500, nil).severity_label.should == "FATAL" Lumberjack::LogEntry.new(Time.now, -1, "test", "app", 1500, nil).severity_label.should == "UNKNOWN" Lumberjack::LogEntry.new(Time.now, 1000, "test", "app", 1500, nil).severity_label.should == "UNKNOWN" end it "should have a message" do entry = Lumberjack::LogEntry.new(Time.now, Lumberjack::Severity::INFO, "test", "app", 1500, "ABCD") entry.message.should == "test" entry.message = "new message" entry.message.should == "new message" end it "should have a progname" do entry = Lumberjack::LogEntry.new(Time.now, Lumberjack::Severity::INFO, "test", "app", 1500, "ABCD") entry.progname.should == "app" entry.progname = "prog" entry.progname.should == "prog" end it "should have a pid" do entry = Lumberjack::LogEntry.new(Time.now, Lumberjack::Severity::INFO, "test", "app", 1500, "ABCD") entry.pid.should == 1500 entry.pid = 150 entry.pid.should == 150 end it "should have a unit_of_work_id" do entry = Lumberjack::LogEntry.new(Time.now, Lumberjack::Severity::INFO, "test", "app", 1500, "ABCD") entry.unit_of_work_id.should == "ABCD" entry.unit_of_work_id = "1234" entry.unit_of_work_id.should == "1234" end it "should be converted to a string" do t = Time.parse("2011-01-29T12:15:32.001") entry = Lumberjack::LogEntry.new(t, Lumberjack::Severity::INFO, "test", "app", 1500, "ABCD") entry.to_s.should == "[2011-01-29T12:15:32.001 INFO app(1500) #ABCD] test" end end lumberjack-1.0.13/spec/lumberjack_spec.rb0000644000004100000410000000150713260145344020360 0ustar www-datawww-datarequire 'spec_helper' describe Lumberjack do context "unit of work" do it "should create a unit work with a unique id in a block" do Lumberjack.unit_of_work_id.should == nil Lumberjack.unit_of_work do id_1 = Lumberjack.unit_of_work_id id_1.should match(/^[0-9a-f]{12}$/) Lumberjack.unit_of_work do id_2 = Lumberjack.unit_of_work_id id_2.should match(/^[0-9a-f]{12}$/) id_2.should_not == id_1 end id_1.should == Lumberjack.unit_of_work_id end Lumberjack.unit_of_work_id.should == nil end it "should allow you to specify a unit of work id for a block" do Lumberjack.unit_of_work("foo") do Lumberjack.unit_of_work_id.should == "foo" end Lumberjack.unit_of_work_id.should == nil end end end lumberjack-1.0.13/spec/logger_spec.rb0000644000004100000410000003504113260145344017520 0ustar www-datawww-datarequire 'spec_helper' require 'pathname' describe Lumberjack::Logger do context "initialization" do before :all do create_tmp_dir end after :all do delete_tmp_dir end it "should wrap an IO stream in a device" do output = StringIO.new logger = Lumberjack::Logger.new(output) logger.device.class.should == Lumberjack::Device::Writer end it "should have a formatter" do output = StringIO.new logger = Lumberjack::Logger.new(output) logger.formatter.should be end it "should open a file path in a device" do logger = Lumberjack::Logger.new(File.join(tmp_dir, "log_file_1.log")) logger.device.class.should == Lumberjack::Device::LogFile end it "should open a pathname in a device" do logger = Lumberjack::Logger.new(Pathname.new(File.join(tmp_dir, "log_file_1.log"))) logger.device.class.should == Lumberjack::Device::LogFile end it "should use the null device if the stream is :null" do logger = Lumberjack::Logger.new(:null) logger.device.class.should == Lumberjack::Device::Null end it "should set the level with a numeric" do logger = Lumberjack::Logger.new(:null, :level => Lumberjack::Severity::WARN) logger.level.should == Lumberjack::Severity::WARN end it "should set the level with a level" do logger = Lumberjack::Logger.new(:null, :level => :warn) logger.level.should == Lumberjack::Severity::WARN end it "should default the level to INFO" do logger = Lumberjack::Logger.new(:null) logger.level.should == Lumberjack::Severity::INFO end it "should set the progname"do logger = Lumberjack::Logger.new(:null, :progname => "app") logger.progname.should == "app" end it "should create a thread to flush the device" do Thread.should_receive(:new) logger = Lumberjack::Logger.new(:null, :flush_seconds => 10) end end context "attributes" do it "should have a level" do logger = Lumberjack::Logger.new logger.level = Lumberjack::Severity::DEBUG logger.level.should == Lumberjack::Severity::DEBUG end it "should have a progname" do logger = Lumberjack::Logger.new logger.progname = "app" logger.progname.should == "app" end it "should be able to silence the log in a block" do output = StringIO.new logger = Lumberjack::Logger.new(output, :buffer_size => 0, :level => Lumberjack::Severity::INFO, :template => ":message") logger.info("one") logger.silence do logger.level.should == Lumberjack::Severity::ERROR logger.info("two") logger.error("three") end logger.info("four") output.string.split.should == ["one", "three", "four"] end it "should be able to customize the level of silence in a block" do output = StringIO.new logger = Lumberjack::Logger.new(output, :buffer_size => 0, :level => Lumberjack::Severity::INFO, :template => ":message") logger.info("one") logger.silence(Lumberjack::Severity::FATAL) do logger.level.should == Lumberjack::Severity::FATAL logger.info("two") logger.error("three") logger.fatal("woof") end logger.info("four") output.string.split.should == ["one", "woof", "four"] end it "should not be able to silence the logger if silencing is disabled" do output = StringIO.new logger = Lumberjack::Logger.new(output, :buffer_size => 0, :level => Lumberjack::Severity::INFO, :template => ":message") logger.silencer = false logger.info("one") logger.silence do logger.level.should == Lumberjack::Severity::INFO logger.info("two") logger.error("three") end logger.info("four") output.string.split.should == ["one", "two", "three", "four"] end it "should be able to set the progname in a block" do logger = Lumberjack::Logger.new logger.set_progname("app") logger.progname.should == "app" block_executed = false logger.set_progname("xxx") do block_executed = true logger.progname.should == "xxx" end block_executed.should == true logger.progname.should == "app" end it "should only affect the current thread when silencing the logger" do output = StringIO.new logger = Lumberjack::Logger.new(output, :buffer_size => 0, :level => Lumberjack::Severity::INFO, :template => ":message") # status is used to make sure the two threads are executing at the same time status = 0 begin Thread.new do logger.silence do logger.info("inner") status = 1 loop{ sleep(0.001); break if status == 2} end end loop{ sleep(0.001); break if status == 1} logger.info("outer") status = 2 logger.close output.string.should include("outer") output.string.should_not include("inner") ensure status = 2 end end it "should only affect the current thread when changing the progname in a block" do output = StringIO.new logger = Lumberjack::Logger.new(output, :progname => "thread1", :buffer_size => 0, :level => Lumberjack::Severity::INFO, :template => ":progname :message") # status is used to make sure the two threads are executing at the same time status = 0 begin Thread.new do logger.set_progname("thread2") do logger.info("inner") status = 1 loop{ sleep(0.001); break if status == 2} end end loop{ sleep(0.001); break if status == 1} logger.info("outer") status = 2 logger.close output.string.should include("thread1") output.string.should include("thread2") ensure status = 2 end end end context "flushing" do it "should autoflush the buffer if it hasn't been flushed in a specified number of seconds" do output = StringIO.new logger = Lumberjack::Logger.new(output, :flush_seconds => 0.1, :level => Lumberjack::Severity::INFO, :template => ":message", :buffer_size => 32767) logger.info("message 1") logger.info("message 2") output.string.should == "" sleep(0.15) output.string.split(Lumberjack::LINE_SEPARATOR).should == ["message 1", "message 2"] logger.info("message 3") output.string.should_not include("message 3") sleep(0.15) output.string.split(Lumberjack::LINE_SEPARATOR).should == ["message 1", "message 2", "message 3"] end it "should write the log entries to the device on flush and update the last flushed time" do output = StringIO.new logger = Lumberjack::Logger.new(output, :level => Lumberjack::Severity::INFO, :template => ":message", :buffer_size => 32767) logger.info("message 1") output.string.should == "" last_flushed_at = logger.last_flushed_at logger.flush output.string.split(Lumberjack::LINE_SEPARATOR).should == ["message 1"] logger.last_flushed_at.should >= last_flushed_at end it "should flush the buffer and close the devices" do output = StringIO.new logger = Lumberjack::Logger.new(output, :level => Lumberjack::Severity::INFO, :template => ":message", :buffer_size => 32767) logger.info("message 1") output.string.should == "" logger.close output.string.split(Lumberjack::LINE_SEPARATOR).should == ["message 1"] output.should be_closed end end context "logging" do let(:output){ StringIO.new } let(:device){ Lumberjack::Device::Writer.new(output, :buffer_size => 0) } let(:logger){ Lumberjack::Logger.new(device, :level => Lumberjack::Severity::INFO, :progname => "test") } let(:n){ Lumberjack::LINE_SEPARATOR } it "should add entries with a numeric severity and a message" do time = Time.parse("2011-01-30T12:31:56.123") Time.stub(:now => time) logger.add(Lumberjack::Severity::INFO, "test") output.string.should == "[2011-01-30T12:31:56.123 INFO test(#{$$}) #] test#{n}" end it "should add entries with a severity label" do time = Time.parse("2011-01-30T12:31:56.123") Time.stub(:now => time) logger.add(:info, "test") output.string.should == "[2011-01-30T12:31:56.123 INFO test(#{$$}) #] test#{n}" end it "should add entries with a custom progname and message" do time = Time.parse("2011-01-30T12:31:56.123") Time.stub(:now => time) logger.add(Lumberjack::Severity::INFO, "test", "app") output.string.should == "[2011-01-30T12:31:56.123 INFO app(#{$$}) #] test#{n}" end it "should add entries with a local progname and message" do time = Time.parse("2011-01-30T12:31:56.123") Time.stub(:now => time) logger.set_progname("block") do logger.add(Lumberjack::Severity::INFO, "test") end output.string.should == "[2011-01-30T12:31:56.123 INFO block(#{$$}) #] test#{n}" end it "should add entries with a progname but no message or block" do time = Time.parse("2011-01-30T12:31:56.123") Time.stub(:now => time) logger.set_progname("default") do logger.add(Lumberjack::Severity::INFO, nil, "message") end output.string.should == "[2011-01-30T12:31:56.123 INFO default(#{$$}) #] message#{n}" end it "should add entries with a block" do time = Time.parse("2011-01-30T12:31:56.123") Time.stub(:now => time) logger.add(Lumberjack::Severity::INFO){"test"} output.string.should == "[2011-01-30T12:31:56.123 INFO test(#{$$}) #] test#{n}" end it "should log entries (::Logger compatibility)" do time = Time.parse("2011-01-30T12:31:56.123") Time.stub(:now => time) logger.log(Lumberjack::Severity::INFO, "test") output.string.should == "[2011-01-30T12:31:56.123 INFO test(#{$$}) #] test#{n}" end it "should append messages with unknown severity to the log" do time = Time.parse("2011-01-30T12:31:56.123") Time.stub(:now => time) logger << "test" output.string.should == "[2011-01-30T12:31:56.123 UNKNOWN test(#{$$}) #] test#{n}" end it "should ouput entries to STDERR if they can't be written the the device" do stderr = $stderr $stderr = StringIO.new begin time = Time.parse("2011-01-30T12:31:56.123") Time.stub(:now => time) device.should_receive(:write).and_raise(StandardError.new("Cannot write to device")) logger.add(Lumberjack::Severity::INFO, "test") $stderr.string.should include("[2011-01-30T12:31:56.123 INFO test(#{$$})] test") $stderr.string.should include("StandardError: Cannot write to device") ensure $stderr = stderr end end context "log helper methods" do let(:device){ Lumberjack::Device::Writer.new(output, :buffer_size => 0, :template => ":message") } it "should only add messages whose severity is greater or equal to the logger level" do logger.add(Lumberjack::Severity::DEBUG, "debug") logger.add(Lumberjack::Severity::INFO, "info") logger.add(Lumberjack::Severity::ERROR, "error") output.string.should == "info#{n}error#{n}" end it "should only log fatal messages when the level is set to fatal" do logger.level = Lumberjack::Severity::FATAL logger.fatal("fatal") logger.fatal?.should == true logger.error("error") logger.error?.should == false logger.warn("warn") logger.warn?.should == false logger.info("info") logger.info?.should == false logger.debug("debug") logger.debug?.should == false logger.unknown("unknown") output.string.should == "fatal#{n}unknown#{n}" end it "should only log error messages and higher when the level is set to error" do logger.level = Lumberjack::Severity::ERROR logger.fatal("fatal") logger.fatal?.should == true logger.error("error") logger.error?.should == true logger.warn("warn") logger.warn?.should == false logger.info("info") logger.info?.should == false logger.debug("debug") logger.debug?.should == false logger.unknown("unknown") output.string.should == "fatal#{n}error#{n}unknown#{n}" end it "should only log warn messages and higher when the level is set to warn" do logger.level = Lumberjack::Severity::WARN logger.fatal("fatal") logger.fatal?.should == true logger.error("error") logger.error?.should == true logger.warn("warn") logger.warn?.should == true logger.info("info") logger.info?.should == false logger.debug("debug") logger.debug?.should == false logger.unknown("unknown") output.string.should == "fatal#{n}error#{n}warn#{n}unknown#{n}" end it "should only log info messages and higher when the level is set to info" do logger.level = Lumberjack::Severity::INFO logger.fatal("fatal") logger.fatal?.should == true logger.error("error") logger.error?.should == true logger.warn("warn") logger.warn?.should == true logger.info("info") logger.info?.should == true logger.debug("debug") logger.debug?.should == false logger.unknown("unknown") output.string.should == "fatal#{n}error#{n}warn#{n}info#{n}unknown#{n}" end it "should log all messages when the level is set to debug" do logger.level = Lumberjack::Severity::DEBUG logger.fatal("fatal") logger.fatal?.should == true logger.error("error") logger.error?.should == true logger.warn("warn") logger.warn?.should == true logger.info("info") logger.info?.should == true logger.debug("debug") logger.debug?.should == true logger.unknown("unknown") output.string.should == "fatal#{n}error#{n}warn#{n}info#{n}debug#{n}unknown#{n}" end it "should only log unkown messages when the level is set above fatal" do logger.level = Lumberjack::Severity::FATAL + 1 logger.fatal("fatal") logger.fatal?.should == false logger.error("error") logger.error?.should == false logger.warn("warn") logger.warn?.should == false logger.info("info") logger.info?.should == false logger.debug("debug") logger.debug?.should == false logger.unknown("unknown") output.string.should == "unknown#{n}" end end end end lumberjack-1.0.13/spec/severity_spec.rb0000644000004100000410000000223413260145344020111 0ustar www-datawww-datarequire 'spec_helper' describe Lumberjack::Severity do it "should convert a level to a label" do Lumberjack::Severity.level_to_label(Lumberjack::Severity::DEBUG).should == "DEBUG" Lumberjack::Severity.level_to_label(Lumberjack::Severity::INFO).should == "INFO" Lumberjack::Severity.level_to_label(Lumberjack::Severity::WARN).should == "WARN" Lumberjack::Severity.level_to_label(Lumberjack::Severity::ERROR).should == "ERROR" Lumberjack::Severity.level_to_label(Lumberjack::Severity::FATAL).should == "FATAL" Lumberjack::Severity.level_to_label(-1).should == "UNKNOWN" end it "should convert a label to a level" do Lumberjack::Severity.label_to_level("DEBUG").should == Lumberjack::Severity::DEBUG Lumberjack::Severity.label_to_level(:info).should == Lumberjack::Severity::INFO Lumberjack::Severity.label_to_level(:warn).should == Lumberjack::Severity::WARN Lumberjack::Severity.label_to_level("Error").should == Lumberjack::Severity::ERROR Lumberjack::Severity.label_to_level("FATAL").should == Lumberjack::Severity::FATAL Lumberjack::Severity.label_to_level("???").should == Lumberjack::Severity::UNKNOWN end end lumberjack-1.0.13/spec/template_spec.rb0000644000004100000410000000362213260145344020054 0ustar www-datawww-datarequire 'spec_helper' describe Lumberjack::Template do let(:time_string){ "2011-01-15T14:23:45.123" } let(:time){ Time.parse(time_string) } let(:entry){ Lumberjack::LogEntry.new(time, Lumberjack::Severity::INFO, "line 1#{Lumberjack::LINE_SEPARATOR}line 2#{Lumberjack::LINE_SEPARATOR}line 3", "app", 12345, "ABCD") } it "should format a log entry with a template string" do template = Lumberjack::Template.new(":message - :severity, :time, :progname@:pid (:unit_of_work_id)") template.call(entry).should == "line 1 - INFO, 2011-01-15T14:23:45.123, app@12345 (ABCD)#{Lumberjack::LINE_SEPARATOR}line 2#{Lumberjack::LINE_SEPARATOR}line 3" end it "should be able to specify the time format for log entries as microseconds" do template = Lumberjack::Template.new(":message (:time)", :time_format => :microseconds) template.call(entry).should == "line 1 (2011-01-15T14:23:45.123000)#{Lumberjack::LINE_SEPARATOR}line 2#{Lumberjack::LINE_SEPARATOR}line 3" end it "should be able to specify the time format for log entries as milliseconds" do template = Lumberjack::Template.new(":message (:time)", :time_format => :milliseconds) template.call(entry).should == "line 1 (2011-01-15T14:23:45.123)#{Lumberjack::LINE_SEPARATOR}line 2#{Lumberjack::LINE_SEPARATOR}line 3" end it "should be able to specify the time format for log entries with a custom format" do template = Lumberjack::Template.new(":message (:time)", :time_format => "%m/%d/%Y, %I:%M:%S %p") template.call(entry).should == "line 1 (01/15/2011, 02:23:45 PM)#{Lumberjack::LINE_SEPARATOR}line 2#{Lumberjack::LINE_SEPARATOR}line 3" end it "should be able to specify a template for additional lines in a message" do template = Lumberjack::Template.new(":message (:time)", :additional_lines => " // :message") template.call(entry).should == "line 1 (2011-01-15T14:23:45.123) // line 2 // line 3" end end lumberjack-1.0.13/spec/rack/0000755000004100000410000000000013260145344015617 5ustar www-datawww-datalumberjack-1.0.13/spec/rack/request_id_spec.rb0000644000004100000410000000402213260145344021320 0ustar www-datawww-datarequire 'spec_helper' describe Lumberjack::Rack::RequestId do it "should use the action dispatch request id if it exists" do app = lambda{|env| [200, {"Content-Type" => env["Content-Type"], "Unit-Of-Work" => Lumberjack.unit_of_work_id.to_s}, ["OK"]]} handler = Lumberjack::Rack::RequestId.new(app) response = handler.call("Content-Type" => "text/plain", "action_dispatch.request_id" => "0123-4567-89ab-cdef") response[0].should == 200 response[1]["Content-Type"].should == "text/plain" response[1]["Unit-Of-Work"].should == "0123-4567-89ab-cdef" response[2].should == ["OK"] end it "should use an abbreviated action dispatch request id if abbreviated is true" do app = lambda{|env| [200, {"Content-Type" => env["Content-Type"], "Unit-Of-Work" => Lumberjack.unit_of_work_id.to_s}, ["OK"]]} handler = Lumberjack::Rack::RequestId.new(app, true) response = handler.call("Content-Type" => "text/plain", "action_dispatch.request_id" => "0123-4567-89ab-cdef") response[0].should == 200 response[1]["Content-Type"].should == "text/plain" response[1]["Unit-Of-Work"].should == "0123" response[2].should == ["OK"] end it "should create a unit of work in a middleware stack if the request id doesn't exist" do app = lambda{|env| [200, {"Content-Type" => env["Content-Type"], "Unit-Of-Work" => Lumberjack.unit_of_work_id.to_s}, ["OK"]]} handler = Lumberjack::Rack::RequestId.new(app) response = handler.call("Content-Type" => "text/plain") response[0].should == 200 response[1]["Content-Type"].should == "text/plain" unit_of_work_1 = response[1]["Unit-Of-Work"] response[2].should == ["OK"] response = handler.call("Content-Type" => "text/html") response[0].should == 200 response[1]["Content-Type"].should == "text/html" unit_of_work_2 = response[1]["Unit-Of-Work"] response[2].should == ["OK"] unit_of_work_1.should_not == nil unit_of_work_2.should_not == nil unit_of_work_1.should_not == unit_of_work_2 end end lumberjack-1.0.13/spec/rack/unit_of_work_spec.rb0000644000004100000410000000161513260145344021666 0ustar www-datawww-datarequire 'spec_helper' describe Lumberjack::Rack::UnitOfWork do it "should create a unit of work in a middleware stack" do app = lambda{|env| [200, {"Content-Type" => env["Content-Type"], "Unit-Of-Work" => Lumberjack.unit_of_work_id.to_s}, ["OK"]]} handler = Lumberjack::Rack::UnitOfWork.new(app) response = handler.call("Content-Type" => "text/plain") response[0].should == 200 response[1]["Content-Type"].should == "text/plain" unit_of_work_1 = response[1]["Unit-Of-Work"] response[2].should == ["OK"] response = handler.call("Content-Type" => "text/html") response[0].should == 200 response[1]["Content-Type"].should == "text/html" unit_of_work_2 = response[1]["Unit-Of-Work"] response[2].should == ["OK"] unit_of_work_1.should_not == nil unit_of_work_2.should_not == nil unit_of_work_1.should_not == unit_of_work_2 end end lumberjack-1.0.13/spec/device/0000755000004100000410000000000013260145344016136 5ustar www-datawww-datalumberjack-1.0.13/spec/device/writer_spec.rb0000644000004100000410000001124413260145344021013 0ustar www-datawww-datarequire 'spec_helper' describe Lumberjack::Device::Writer do let(:time_string){ "2011-01-15T14:23:45.123" } let(:time){ Time.parse(time_string) } let(:stream){ StringIO.new } let(:entry){ Lumberjack::LogEntry.new(time, Lumberjack::Severity::INFO, "test message", "app", 12345, "ABCD") } it "should buffer output and not write directly to the stream" do device = Lumberjack::Device::Writer.new(stream, :template => ":message", :buffer_size => 32767) device.write(entry) stream.string.should == "" device.flush stream.string.should == "test message#{Lumberjack::LINE_SEPARATOR}" end it "should flush the buffer when it gets to the specified size" do device = Lumberjack::Device::Writer.new(stream, :buffer_size => 15, :template => ":message") device.write(entry) stream.string.should == "" device.write(entry) stream.string.should == "test message#{Lumberjack::LINE_SEPARATOR}test message#{Lumberjack::LINE_SEPARATOR}" end it "should sync the stream and flush it when the device is flushed" do # Create an IO like object that require flush to be called io = Object.new def io.init; @string = ""; @buffer = ""; @sync = false; end def io.write(string); @buffer << string; end def io.flush; @string << @buffer; @buffer = ""; end def io.string; @string; end def io.sync=(val); @sync = val; end def io.sync; @sync; end io.init device = Lumberjack::Device::Writer.new(io, :template => ":message", :buffer_size => 32767) device.write(entry) io.string.should == "" device.flush io.string.should == "test message#{Lumberjack::LINE_SEPARATOR}" io.sync.should == true end it "should be able to set the buffer size" do device = Lumberjack::Device::Writer.new(stream, :buffer_size => 15) device.buffer_size.should == 15 device.buffer_size = 100 device.buffer_size.should == 100 end it "should have a default buffer size of 0" do device = Lumberjack::Device::Writer.new(stream) device.buffer_size.should == 0 end it "should write entries out to the stream with a default template" do device = Lumberjack::Device::Writer.new(stream) device.write(entry) device.flush stream.string.should == "[2011-01-15T14:23:45.123 INFO app(12345) #ABCD] test message#{Lumberjack::LINE_SEPARATOR}" end it "should write entries out to the stream with a custom template" do device = Lumberjack::Device::Writer.new(stream, :template => ":message") device.write(entry) device.flush stream.string.should == "test message#{Lumberjack::LINE_SEPARATOR}" end it "should be able to specify the time format for the template" do device = Lumberjack::Device::Writer.new(stream, :time_format => :microseconds) device.write(entry) device.flush stream.string.should == "[2011-01-15T14:23:45.123000 INFO app(12345) #ABCD] test message#{Lumberjack::LINE_SEPARATOR}" end it "should be able to specify a block template for log entries" do device = Lumberjack::Device::Writer.new(stream, :template => lambda{|e| e.message.upcase}) device.write(entry) device.flush stream.string.should == "TEST MESSAGE#{Lumberjack::LINE_SEPARATOR}" end it "should write to STDERR if an error is raised when flushing to the stream" do stderr = $stderr $stderr = StringIO.new begin device = Lumberjack::Device::Writer.new(stream, :template => ":message") stream.should_receive(:write).and_raise(StandardError.new("Cannot write to stream")) device.write(entry) device.flush $stderr.string.should include("test message#{Lumberjack::LINE_SEPARATOR}") $stderr.string.should include("StandardError: Cannot write to stream") ensure $stderr = stderr end end context "multi line messages" do let(:message){ "line 1#{Lumberjack::LINE_SEPARATOR}line 2#{Lumberjack::LINE_SEPARATOR}line 3" } let(:entry){ Lumberjack::LogEntry.new(time, Lumberjack::Severity::INFO, message, "app", 12345, "ABCD") } it "should have a default template for multiline messages" do device = Lumberjack::Device::Writer.new(stream) device.write(entry) device.flush stream.string.split(Lumberjack::LINE_SEPARATOR).should == ["[2011-01-15T14:23:45.123 INFO app(12345) #ABCD] line 1", "> [#ABCD] line 2", "> [#ABCD] line 3"] end it "should be able to specify a template for multiple line messages" do device = Lumberjack::Device::Writer.new(stream, :additional_lines => " // :message") device.write(entry) device.flush stream.string.should == "[2011-01-15T14:23:45.123 INFO app(12345) #ABCD] line 1 // line 2 // line 3#{Lumberjack::LINE_SEPARATOR}" end end end lumberjack-1.0.13/spec/device/rolling_log_file_spec.rb0000644000004100000410000001254513260145344023012 0ustar www-datawww-datarequire 'spec_helper' describe Lumberjack::Device::RollingLogFile do before :all do Lumberjack::Device::SizeRollingLogFile #needed by jruby create_tmp_dir end after :all do delete_tmp_dir end before :each do delete_tmp_files end let(:entry){ Lumberjack::LogEntry.new(Time.now, 1, "New log entry", nil, $$, nil) } it "should check for rolling the log file on flush" do device = Lumberjack::Device::RollingLogFile.new(File.join(tmp_dir, "test.log"), :buffer_size => 32767, :min_roll_check => 0) device.write(entry) expect(device).to receive(:roll_file?).twice.and_return(false) device.flush device.close end it "should roll the file by archiving the existing file and opening a new stream and calling after_roll" do log_file = File.join(tmp_dir, "test_2.log") device = Lumberjack::Device::RollingLogFile.new(log_file, :template => ":message", :buffer_size => 32767, :min_roll_check => 0) expect(device).to receive(:roll_file?).and_return(false, true) expect(device).to receive(:after_roll) device.stub(:archive_file_suffix => "rolled") device.write(entry) device.flush device.write(Lumberjack::LogEntry.new(Time.now, 1, "Another log entry", nil, $$, nil)) device.close File.read("#{log_file}.rolled").should == "New log entry#{Lumberjack::LINE_SEPARATOR}" File.read(log_file).should == "Another log entry#{Lumberjack::LINE_SEPARATOR}" end it "should reopen the file if the stream inode doesn't match the file path inode" do log_file = File.join(tmp_dir, "test_3.log") device = Lumberjack::Device::RollingLogFile.new(log_file, :template => ":message", :min_roll_check => 0) device.stub(:roll_file? => false) device.write(entry) device.flush File.rename(log_file, "#{log_file}.rolled") device.flush device.write(Lumberjack::LogEntry.new(Time.now, 1, "Another log entry", nil, $$, nil)) device.close File.read("#{log_file}.rolled").should == "New log entry#{Lumberjack::LINE_SEPARATOR}" File.read(log_file).should == "Another log entry#{Lumberjack::LINE_SEPARATOR}" end it "should roll the file properly with multiple thread and processes using it" do log_file = File.join(tmp_dir, "test_4.log") process_count = 8 thread_count = 4 entry_count = 400 max_size = 128 severity = Lumberjack::Severity::INFO message = "This is a test message that is written to the log file to indicate what the state of the application is." logger_test = lambda do device = Lumberjack::Device::SizeRollingLogFile.new(log_file, :max_size => max_size, :template => ":message", :buffer_size => 32767, :min_roll_check => 0) threads = [] thread_count.times do threads << Thread.new do entry_count.times do |i| device.write(Lumberjack::LogEntry.new(Time.now, severity, message, "test", $$, nil)) device.flush if i % 10 == 0 end device.flush end end threads.each{|thread| thread.value} device.close end # Process.fork is unavailable on jruby so we need to use the java threads instead. if RUBY_PLATFORM.match(/java/) outer_threads = [] process_count.times do outer_threads << Thread.new(&logger_test) end outer_threads.each{|thread| thread.value} else process_count.times do Process.fork(&logger_test) end Process.waitall end line_count = 0 file_count = 0 Dir.glob("#{log_file}*").each do |file| file_count += 1 lines = File.read(file).split(Lumberjack::LINE_SEPARATOR) line_count += lines.size lines.each do |line| line.should == message end end file_count.should > 3 end it "should only keep a specified number of archived log files" do log_file = File.join(tmp_dir, "test_5.log") device = Lumberjack::Device::RollingLogFile.new(log_file, :template => ":message", :keep => 2, :buffer_size => 32767, :min_roll_check => 0) expect(device).to receive(:roll_file?).and_return(false, true, true, true) expect(device).to receive(:archive_file_suffix).and_return("delete", "another", "keep") t = Time.now expect(File).to receive(:ctime).with("#{log_file}.delete").at_least(1).times.and_return(t + 1) expect(File).to receive(:ctime).with("#{log_file}.another").at_least(1).times.and_return(t + 2) expect(File).to receive(:ctime).with("#{log_file}.keep").at_least(1).times.and_return(t + 3) device.write(entry) device.flush device.write(entry) device.flush device.write(entry) device.flush device.write(entry) device.close Dir.glob("#{log_file}*").sort.should == [log_file, "#{log_file}.another", "#{log_file}.keep"] end context "when file is rolled" do let(:log_file) { File.join(tmp_dir, "test_6.log") } let(:device) do device = Lumberjack::Device::RollingLogFile.new(log_file, :template => ":message", :keep => 2, :buffer_size => 32767, :min_roll_check => 0) device.stub(:roll_file?).and_return(true) device.stub(:archive_file_suffix => "rolled") device end before do device.write(entry) device.flush end it "reopens file with proper encoding" do encoding = device.send(:stream).external_encoding expect(encoding).to_not be_nil expect(encoding.name).to eq "ASCII-8BIT" end end end lumberjack-1.0.13/spec/device/date_rolling_log_file_spec.rb0000644000004100000410000000454713260145344024012 0ustar www-datawww-datarequire 'spec_helper' describe Lumberjack::Device::DateRollingLogFile do before :all do create_tmp_dir end after :all do delete_tmp_dir end before :each do delete_tmp_files end let(:one_day){ 60 * 60 * 24 } it "should roll the file daily" do now = Time.now log_file = File.join(tmp_dir, "a#{rand(1000000000)}.log") device = Lumberjack::Device::DateRollingLogFile.new(log_file, :roll => :daily, :template => ":message", :min_roll_check => 0) logger = Lumberjack::Logger.new(device, :buffer_size => 2) Timecop.travel(now) do logger.error("test day one") logger.flush end Timecop.travel(now + one_day) do logger.error("test day two") logger.close end File.read("#{log_file}.#{now.to_date.strftime('%Y-%m-%d')}").should == "test day one#{Lumberjack::LINE_SEPARATOR}" File.read(log_file).should == "test day two#{Lumberjack::LINE_SEPARATOR}" end it "should roll the file weekly" do now = Time.now log_file = File.join(tmp_dir, "b#{rand(1000000000)}.log") device = Lumberjack::Device::DateRollingLogFile.new(log_file, :roll => :weekly, :template => ":message", :min_roll_check => 0) logger = Lumberjack::Logger.new(device, :buffer_size => 2) Timecop.freeze(now) do logger.error("test week one") logger.flush end Timecop.freeze(now + (7 * one_day)) do logger.error("test week two") logger.close end File.read("#{log_file}.#{now.to_date.strftime('week-of-%Y-%m-%d')}").should == "test week one#{Lumberjack::LINE_SEPARATOR}" File.read(log_file).should == "test week two#{Lumberjack::LINE_SEPARATOR}" end it "should roll the file monthly" do now = Time.now log_file = File.join(tmp_dir, "c#{rand(1000000000)}.log") device = Lumberjack::Device::DateRollingLogFile.new(log_file, :roll => :monthly, :template => ":message", :min_roll_check => 0) logger = Lumberjack::Logger.new(device, :buffer_size => 2) Timecop.freeze(now) do logger.error("test month one") logger.flush end Timecop.freeze(now + (31 * one_day)) do logger.error("test month two") logger.close end File.read("#{log_file}.#{now.to_date.strftime('%Y-%m')}").should == "test month one#{Lumberjack::LINE_SEPARATOR}" File.read(log_file).should == "test month two#{Lumberjack::LINE_SEPARATOR}" end end lumberjack-1.0.13/spec/device/size_rolling_log_file_spec.rb0000644000004100000410000000403613260145344024040 0ustar www-datawww-datarequire 'spec_helper' describe Lumberjack::Device::SizeRollingLogFile do before :all do create_tmp_dir end after :all do delete_tmp_dir end before :each do delete_tmp_files end it "should roll a file when it gets to a specified size" do log_file = File.join(tmp_dir, "a#{rand(1000000000)}.log") device = Lumberjack::Device::SizeRollingLogFile.new(log_file, :max_size => 40, :template => ":message", :min_roll_check => 0) logger = Lumberjack::Logger.new(device, :buffer_size => 2) 4.times do |i| logger.error("test message #{i + 1}") logger.flush end logger.close File.read("#{log_file}.1").split(Lumberjack::LINE_SEPARATOR).should == ["test message 1", "test message 2", "test message 3"] File.read(log_file).should == "test message 4#{Lumberjack::LINE_SEPARATOR}" end it "should be able to specify the max size in kilobytes" do log_file = File.join(tmp_dir, "b#{rand(1000000000)}.log") device = Lumberjack::Device::SizeRollingLogFile.new(log_file, :max_size => "32K", :min_roll_check => 0) device.max_size.should == 32768 end it "should be able to specify the max size in megabytes" do log_file = File.join(tmp_dir, "c#{rand(1000000000)}.log") device = Lumberjack::Device::SizeRollingLogFile.new(log_file, :max_size => "100M", :min_roll_check => 0) device.max_size.should == 104_857_600 end it "should be able to specify the max size in gigabytes" do log_file = File.join(tmp_dir, "d#{rand(1000000000)}.log") device = Lumberjack::Device::SizeRollingLogFile.new(log_file, :max_size => "1G", :min_roll_check => 0) device.max_size.should == 1_073_741_824 end it "should figure out the next archive file name available" do log_file = File.join(tmp_dir, "filename.log") (3..11).each do |i| File.open("#{log_file}.#{i}", 'w'){|f| f.write(i.to_s)} end device = Lumberjack::Device::SizeRollingLogFile.new(log_file, :max_size => "100M", :min_roll_check => 0) device.archive_file_suffix.should == "12" end end lumberjack-1.0.13/spec/device/log_file_spec.rb0000644000004100000410000000240313260145344021254 0ustar www-datawww-data# -*- coding: utf-8 -*- require 'spec_helper' describe Lumberjack::Device::LogFile do before :all do create_tmp_dir end after :all do delete_tmp_dir end before :each do delete_tmp_files end it "should append to a file" do log_file = File.join(tmp_dir, "a#{rand(1000000000)}.log") File.open(log_file, 'w') do |f| f.puts("Existing contents") end device = Lumberjack::Device::LogFile.new(log_file, :template => ":message") device.write(Lumberjack::LogEntry.new(Time.now, 1, "New log entry", nil, $$, nil)) device.close File.read(log_file).should == "Existing contents\nNew log entry#{Lumberjack::LINE_SEPARATOR}" end it "properly handles messages with broken UTF-8 characters" do log_file = File.join(tmp_dir, "test_6.log") device = Lumberjack::Device::LogFile.new(log_file, :keep => 2, :buffer_size => 32767) message = [0xC4, 0x90, 0xE1, 0xBB].pack("c*").force_encoding "ASCII-8BIT" entry = Lumberjack::LogEntry.new(Time.now, 1, message, nil, $$, nil) device.write entry message = "проверка" entry = Lumberjack::LogEntry.new(Time.now, 1, message, nil, $$, nil) device.write entry expect do device.flush end.to_not raise_error end end lumberjack-1.0.13/spec/device/null_spec.rb0000644000004100000410000000042413260145344020447 0ustar www-datawww-datarequire 'spec_helper' describe Lumberjack::Device::Null do it "should not generate any output" do device = Lumberjack::Device::Null.new device.write(Lumberjack::LogEntry.new(Time.now, 1, "New log entry", nil, $$, nil)) device.flush device.close end end lumberjack-1.0.13/spec/formatter_spec.rb0000644000004100000410000000266013260145344020245 0ustar www-datawww-datarequire 'spec_helper' describe Lumberjack::Formatter do let(:formatter){ Lumberjack::Formatter.new } it "should have a default set of formatters" do formatter.format("abc").should == "abc" formatter.format([1, 2, 3]).should == "[1, 2, 3]" formatter.format(ArgumentError.new("boom")).should == "ArgumentError: boom" end it "should be able to add a formatter object for a class" do formatter.add(Numeric, lambda{|obj| "number: #{obj}"}) formatter.format(10).should == "number: 10" end it "should be able to add a formatter block for a class" do formatter.add(Numeric){|obj| "number: #{obj}"} formatter.format(10).should == "number: 10" end it "should be able to remove a formatter for a class" do formatter.remove(String) formatter.format("abc").should == "\"abc\"" end it "should be able to chain add and remove calls" do formatter.remove(String).should == formatter formatter.add(String, Lumberjack::Formatter::StringFormatter.new).should == formatter end it "should format an object based on the class hierarchy" do formatter.add(Numeric){|obj| "number: #{obj}"} formatter.add(Integer){|obj| "fixed number: #{obj}"} formatter.format(10).should == "fixed number: 10" formatter.format(10.1).should == "number: 10.1" end it "should have a default formatter" do formatter.remove(Object) formatter.format(:test).should == ":test" end end lumberjack-1.0.13/lib/0000755000004100000410000000000013260145344014513 5ustar www-datawww-datalumberjack-1.0.13/lib/lumberjack.rb0000644000004100000410000000324113260145344017157 0ustar www-datawww-data# frozen_string_literals: true require 'rbconfig' require 'time' require 'thread' require 'securerandom' module Lumberjack LINE_SEPARATOR = (RbConfig::CONFIG['host_os'].match(/mswin/i) ? "\r\n" : "\n") require File.expand_path("../lumberjack/severity.rb", __FILE__) require File.expand_path("../lumberjack/log_entry.rb", __FILE__) require File.expand_path("../lumberjack/formatter.rb", __FILE__) require File.expand_path("../lumberjack/device.rb", __FILE__) require File.expand_path("../lumberjack/logger.rb", __FILE__) require File.expand_path("../lumberjack/template.rb", __FILE__) require File.expand_path("../lumberjack/rack.rb", __FILE__) class << self # Define a unit of work within a block. Within the block supplied to this # method, calling +unit_of_work_id+ will return the same value that can # This can then be used for tying together log entries. # # You can specify the id for the unit of work if desired. If you don't supply # it, a 12 digit hexidecimal number will be automatically generated for you. # # For the common use case of treating a single web request as a unit of work, see the # Lumberjack::Rack::UnitOfWork class. def unit_of_work(id = nil) save_val = Thread.current[:lumberjack_logger_unit_of_work_id] id ||= SecureRandom.hex(6) Thread.current[:lumberjack_logger_unit_of_work_id] = id begin return yield ensure Thread.current[:lumberjack_logger_unit_of_work_id] = save_val end end # Get the UniqueIdentifier for the current unit of work. def unit_of_work_id Thread.current[:lumberjack_logger_unit_of_work_id] end end end lumberjack-1.0.13/lib/lumberjack/0000755000004100000410000000000013260145344016632 5ustar www-datawww-datalumberjack-1.0.13/lib/lumberjack/formatter.rb0000644000004100000410000000602313260145344021163 0ustar www-datawww-data# frozen_string_literals: true module Lumberjack # This class controls the conversion of log entry messages into strings. This allows you # to log any object you want and have the logging system worry about converting it into a string. # # Formats are added to a Formatter by associating them with a class using the +add+ method. Formats # are any object that responds to the +call+ method. # # By default, all object will be converted to strings using their inspect method except for Strings # and Exceptions. Strings are not converted and Exceptions are converted using the ExceptionFormatter. class Formatter require File.expand_path("../formatter/exception_formatter.rb", __FILE__) require File.expand_path("../formatter/inspect_formatter.rb", __FILE__) require File.expand_path("../formatter/pretty_print_formatter.rb", __FILE__) require File.expand_path("../formatter/string_formatter.rb", __FILE__) def initialize @class_formatters = {} @_default_formatter = InspectFormatter.new add(Object, @_default_formatter) add(String, :string) add(Exception, :exception) end # Add a formatter for a class. The formatter can be specified as either an object # that responds to the +call+ method or as a symbol representing one of the predefined # formatters, or as a block to the method call. # # The predefined formatters are: :inspect, :string, :exception, and :pretty_print. # # === Examples # # # Use a predefined formatter # formatter.add(MyClass, :pretty_print) # # # Pass in a formatter object # formatter.add(MyClass, Lumberjack::Formatter::PrettyPrintFormatter.new) # # # Use a block # formatter.add(MyClass){|obj| obj.humanize} # # # Add statements can be chained together # formatter.add(MyClass, :pretty_print).add(YourClass){|obj| obj.humanize} def add(klass, formatter = nil, &block) formatter ||= block if formatter.is_a?(Symbol) formatter_class_name = "#{formatter.to_s.gsub(/(^|_)([a-z])/){|m| $~[2].upcase}}Formatter" formatter = Formatter.const_get(formatter_class_name).new end @class_formatters[klass] = formatter self end # Remove the formatter associated with a class. Remove statements can be chained together. def remove(klass) @class_formatters.delete(klass) self end # Format a message object as a string. def format(message) formatter_for(message.class).call(message) end # Hack for compatibility with Logger::Formatter def call(severity, timestamp, progname, msg) "#{format(msg)}\n" end private # Find the formatter for a class by looking it up using the class hierarchy. def formatter_for(klass) #:nodoc: while klass != nil do formatter = @class_formatters[klass] return formatter if formatter klass = klass.superclass end @_default_formatter end end end lumberjack-1.0.13/lib/lumberjack/rack.rb0000644000004100000410000000031413260145344020075 0ustar www-datawww-data# frozen_string_literals: true module Lumberjack module Rack require File.expand_path("../rack/unit_of_work.rb", __FILE__) require File.expand_path("../rack/request_id.rb", __FILE__) end end lumberjack-1.0.13/lib/lumberjack/device.rb0000644000004100000410000000174113260145344020421 0ustar www-datawww-data# frozen_string_literals: true module Lumberjack # This is an abstract class for logging devices. Subclasses must implement the +write+ method and # may implement the +close+ and +flush+ methods if applicable. class Device require File.expand_path("../device/writer.rb", __FILE__) require File.expand_path("../device/log_file.rb", __FILE__) require File.expand_path("../device/rolling_log_file.rb", __FILE__) require File.expand_path("../device/date_rolling_log_file.rb", __FILE__) require File.expand_path("../device/size_rolling_log_file.rb", __FILE__) require File.expand_path("../device/null.rb", __FILE__) # Subclasses must implement this method to write a LogEntry. def write(entry) raise NotImplementedError end # Subclasses may implement this method to close the device. def close flush end # Subclasses may implement this method to flush any buffers used by the device. def flush end end end lumberjack-1.0.13/lib/lumberjack/formatter/0000755000004100000410000000000013260145344020635 5ustar www-datawww-datalumberjack-1.0.13/lib/lumberjack/formatter/string_formatter.rb0000644000004100000410000000031713260145344024554 0ustar www-datawww-data# frozen_string_literals: true module Lumberjack class Formatter # Format an object by calling +to_s+ on it. class StringFormatter def call(obj) obj.to_s end end end end lumberjack-1.0.13/lib/lumberjack/formatter/exception_formatter.rb0000644000004100000410000000064613260145344025251 0ustar www-datawww-data# frozen_string_literals: true module Lumberjack class Formatter # Format an exception including the backtrace. class ExceptionFormatter def call(exception) message = "#{exception.class.name}: #{exception.message}" message << "#{Lumberjack::LINE_SEPARATOR} #{exception.backtrace.join("#{Lumberjack::LINE_SEPARATOR} ")}" if exception.backtrace message end end end end lumberjack-1.0.13/lib/lumberjack/formatter/pretty_print_formatter.rb0000644000004100000410000000105313260145344026007 0ustar www-datawww-data# frozen_string_literals: true require 'pp' require 'stringio' module Lumberjack class Formatter # Format an object with it's pretty print method. class PrettyPrintFormatter attr_accessor :width # Create a new formatter. The maximum width of the message can be specified with the width # parameter (defaults to 79 characters). def initialize(width = 79) @width = width end def call(obj) s = StringIO.new PP.pp(obj, s) s.string.chomp end end end end lumberjack-1.0.13/lib/lumberjack/formatter/inspect_formatter.rb0000644000004100000410000000032613260145344024713 0ustar www-datawww-data# frozen_string_literals: true module Lumberjack class Formatter # Format an object by calling +inspect+ on it. class InspectFormatter def call(obj) obj.inspect end end end end lumberjack-1.0.13/lib/lumberjack/logger.rb0000644000004100000410000002461713260145344020450 0ustar www-datawww-data# frozen_string_literals: true module Lumberjack # Logger is a thread safe logging object. It has a compatible API with the Ruby # standard library Logger class, the Log4r gem, and ActiveSupport::BufferedLogger. # # === Example # # logger = Lumberjack::Logger.new # logger.info("Starting processing") # logger.debug("Processing options #{options.inspect}") # logger.fatal("OMG the application is on fire!") # # Log entries are written to a logging Device if their severity meets or exceeds the log level. # # Devices may use buffers internally and the log entries are not guaranteed to be written until you call # the +flush+ method. Sometimes this can result in problems when trying to track down extraordinarily # long running sections of code since it is likely that none of the messages logged before the long # running code will appear in the log until the entire process finishes. You can set the +:flush_seconds+ # option on the constructor to force the device to be flushed periodically. This will create a new # monitoring thread, but its use is highly recommended. # # Each log entry records the log message and severity along with the time it was logged, the # program name, process id, and unit of work id. The message will be converted to a string, but # otherwise, it is up to the device how these values are recorded. Messages are converted to strings # using a Formatter associated with the logger. class Logger include Severity # The time that the device was last flushed. attr_reader :last_flushed_at # The name of the program associated with log messages. attr_writer :progname # The device being written to. attr_reader :device # Set +silencer+ to false to disable silencing the log. attr_accessor :silencer # Create a new logger to log to a Device. # # The +device+ argument can be in any one of several formats. # # If it is a Device object, that object will be used. # If it has a +write+ method, it will be wrapped in a Device::Writer class. # If it is :null, it will be a Null device that won't record any output. # Otherwise, it will be assumed to be file path and wrapped in a Device::LogFile class. # # This method can take the following options: # # * :level - The logging level below which messages will be ignored. # * :progname - The name of the program that will be recorded with each log entry. # * :flush_seconds - The maximum number of seconds between flush calls. # * :roll - If the log device is a file path, it will be a Device::DateRollingLogFile if this is set. # * :max_size - If the log device is a file path, it will be a Device::SizeRollingLogFile if this is set. # # All other options are passed to the device constuctor. def initialize(device = STDOUT, options = {}) @thread_settings = {} options = options.dup self.level = options.delete(:level) || INFO self.progname = options.delete(:progname) max_flush_seconds = options.delete(:flush_seconds).to_f @device = open_device(device, options) @_formatter = Formatter.new @last_flushed_at = Time.now @silencer = true create_flusher_thread(max_flush_seconds) if max_flush_seconds > 0 end # Get the Formatter object used to convert messages into strings. def formatter @_formatter end # Get the level of severity of entries that are logged. Entries with a lower # severity level will be ignored. def level thread_local_value(:lumberjack_logger_level) || @level end # Add a message to the log with a given severity. The message can be either # passed in the +message+ argument or supplied with a block. This method # is not normally called. Instead call one of the helper functions # +fatal+, +error+, +warn+, +info+, or +debug+. # # The severity can be passed in either as one of the Severity constants, # or as a Severity label. # # === Example # # logger.add(Lumberjack::Severity::ERROR, exception) # logger.add(Lumberjack::Severity::INFO, "Request completed") # logger.add(:warn, "Request took a long time") # logger.add(Lumberjack::Severity::DEBUG){"Start processing with options #{options.inspect}"} def add(severity, message = nil, progname = nil) severity = Severity.label_to_level(severity) if severity.is_a?(String) || severity.is_a?(Symbol) return unless severity && severity >= level time = Time.now if message.nil? if block_given? message = yield else message = progname progname = nil end end message = @_formatter.format(message) progname ||= self.progname entry = LogEntry.new(time, severity, message, progname, $$, Lumberjack.unit_of_work_id) begin device.write(entry) rescue => e $stderr.puts("#{e.class.name}: #{e.message}#{' at ' + e.backtrace.first if e.backtrace}") $stderr.puts(entry.to_s) end nil end alias_method :log, :add # Flush the logging device. Messages are not guaranteed to be written until this method is called. def flush device.flush @last_flushed_at = Time.now nil end # Close the logging device. def close flush @device.close if @device.respond_to?(:close) end # Log a +FATAL+ message. The message can be passed in either the +message+ argument or in a block. def fatal(message = nil, progname = nil, &block) add(FATAL, message, progname, &block) end # Return +true+ if +FATAL+ messages are being logged. def fatal? level <= FATAL end # Log an +ERROR+ message. The message can be passed in either the +message+ argument or in a block. def error(message = nil, progname = nil, &block) add(ERROR, message, progname, &block) end # Return +true+ if +ERROR+ messages are being logged. def error? level <= ERROR end # Log a +WARN+ message. The message can be passed in either the +message+ argument or in a block. def warn(message = nil, progname = nil, &block) add(WARN, message, progname, &block) end # Return +true+ if +WARN+ messages are being logged. def warn? level <= WARN end # Log an +INFO+ message. The message can be passed in either the +message+ argument or in a block. def info(message = nil, progname = nil, &block) add(INFO, message, progname, &block) end # Return +true+ if +INFO+ messages are being logged. def info? level <= INFO end # Log a +DEBUG+ message. The message can be passed in either the +message+ argument or in a block. def debug(message = nil, progname = nil, &block) add(DEBUG, message, progname, &block) end # Return +true+ if +DEBUG+ messages are being logged. def debug? level <= DEBUG end # Log a message when the severity is not known. Unknown messages will always appear in the log. # The message can be passed in either the +message+ argument or in a block. def unknown(message = nil, progname = nil, &block) add(UNKNOWN, message, progname, &block) end alias_method :<<, :unknown # Set the minimum level of severity of messages to log. def level=(severity) if severity.is_a?(Integer) @level = severity else @level = Severity.label_to_level(severity) end end # Silence the logger by setting a new log level inside a block. By default, only +ERROR+ or +FATAL+ # messages will be logged. # # === Example # # logger.level = Lumberjack::Severity::INFO # logger.silence do # do_something # Log level inside the block is +ERROR+ # end def silence(temporary_level = ERROR, &block) if silencer push_thread_local_value(:lumberjack_logger_level, temporary_level, &block) else yield end end # Set the program name that is associated with log messages. If a block # is given, the program name will be valid only within the block. def set_progname(value, &block) if block push_thread_local_value(:lumberjack_logger_progname, value, &block) else self.progname = value end end # Get the program name associated with log messages. def progname thread_local_value(:lumberjack_logger_progname) || @progname end private # Set a local value for a thread tied to this object. def set_thread_local_value(name, value) #:nodoc: values = Thread.current[name] unless values values = {} Thread.current[name] = values end if value.nil? values.delete(self) Thread.current[name] = nil if values.empty? else values[self] = value end end # Get a local value for a thread tied to this object. def thread_local_value(name) #:nodoc: values = Thread.current[name] values[self] if values end # Set a local value for a thread tied to this object within a block. def push_thread_local_value(name, value) #:nodoc: save_val = thread_local_value(name) set_thread_local_value(name, value) begin yield ensure set_thread_local_value(name, save_val) end end # Open a logging device. def open_device(device, options) #:nodoc: if device.is_a?(Device) device elsif device.respond_to?(:write) && device.respond_to?(:flush) Device::Writer.new(device, options) elsif device == :null Device::Null.new else device = device.to_s if options[:roll] Device::DateRollingLogFile.new(device, options) elsif options[:max_size] Device::SizeRollingLogFile.new(device, options) else Device::LogFile.new(device, options) end end end # Create a thread that will periodically call flush. def create_flusher_thread(flush_seconds) #:nodoc: if flush_seconds > 0 begin logger = self Thread.new do loop do begin sleep(flush_seconds) logger.flush if Time.now - logger.last_flushed_at >= flush_seconds rescue => e STDERR.puts("Error flushing log: #{e.inspect}") end end end end end end end end lumberjack-1.0.13/lib/lumberjack/severity.rb0000644000004100000410000000102413260145344021026 0ustar www-datawww-data# frozen_string_literals: true module Lumberjack # The standard severity levels for logging messages. module Severity UNKNOWN = 5 FATAL = 4 ERROR = 3 WARN = 2 INFO = 1 DEBUG = 0 SEVERITY_LABELS = %w(DEBUG INFO WARN ERROR FATAL UNKNOWN).freeze class << self def level_to_label(severity) SEVERITY_LABELS[severity] || SEVERITY_LABELS.last end def label_to_level(label) SEVERITY_LABELS.index(label.to_s.upcase) || UNKNOWN end end end end lumberjack-1.0.13/lib/lumberjack/log_entry.rb0000644000004100000410000000204013260145344021155 0ustar www-datawww-data# frozen_string_literals: true module Lumberjack # An entry in a log is a data structure that captures the log message as well as # information about the system that logged the message. class LogEntry attr_accessor :time, :message, :severity, :progname, :pid, :unit_of_work_id TIME_FORMAT = "%Y-%m-%dT%H:%M:%S".freeze def initialize(time, severity, message, progname, pid, unit_of_work_id) @time = time @severity = (severity.is_a?(Integer) ? severity : Severity.label_to_level(severity)) @message = message @progname = progname @pid = pid @unit_of_work_id = unit_of_work_id end def severity_label Severity.level_to_label(severity) end def to_s buf = "[#{time.strftime(TIME_FORMAT)}.#{(time.usec / 1000.0).round.to_s.rjust(3, '0')} #{severity_label} #{progname}(#{pid})" if unit_of_work_id buf << " #" buf << unit_of_work_id end buf << "] " buf << message end def inspect to_s end end end lumberjack-1.0.13/lib/lumberjack/rack/0000755000004100000410000000000013260145344017552 5ustar www-datawww-datalumberjack-1.0.13/lib/lumberjack/rack/request_id.rb0000644000004100000410000000135313260145344022245 0ustar www-datawww-data# frozen_string_literals: true module Lumberjack module Rack # Support for using the Rails ActionDispatch request id in the log. # The format is expected to be a random UUID and only the first chunk is used for terseness # if the abbreviated argument is true. class RequestId REQUEST_ID = "action_dispatch.request_id".freeze def initialize(app, abbreviated = false) @app = app @abbreviated = abbreviated end def call(env) request_id = env[REQUEST_ID] if request_id && @abbreviated request_id = request_id.split('-'.freeze, 2).first end Lumberjack.unit_of_work(request_id) do @app.call(env) end end end end end lumberjack-1.0.13/lib/lumberjack/rack/unit_of_work.rb0000644000004100000410000000041313260145344022602 0ustar www-datawww-data# frozen_string_literals: true module Lumberjack module Rack class UnitOfWork def initialize(app) @app = app end def call(env) Lumberjack.unit_of_work do @app.call(env) end end end end end lumberjack-1.0.13/lib/lumberjack/device/0000755000004100000410000000000013260145344020071 5ustar www-datawww-datalumberjack-1.0.13/lib/lumberjack/device/log_file.rb0000644000004100000410000000117713260145344022204 0ustar www-datawww-data# frozen_string_literals: true require 'fileutils' module Lumberjack class Device # This is a logging device that appends log entries to a file. class LogFile < Writer EXTERNAL_ENCODING = "ascii-8bit" # The absolute path of the file being logged to. attr_reader :path # Create a logger to the file at +path+. Options are passed through to the Writer constructor. def initialize(path, options = {}) @path = File.expand_path(path) FileUtils.mkdir_p(File.dirname(@path)) super(File.new(@path, 'a', :encoding => EXTERNAL_ENCODING), options) end end end end lumberjack-1.0.13/lib/lumberjack/device/size_rolling_log_file.rb0000644000004100000410000000372013260145344024760 0ustar www-datawww-data# frozen_string_literals: true module Lumberjack class Device # This is a log device that appends entries to a file and rolls the file when it reaches a specified # size threshold. When a file is rolled, it will have an number extension appended to the file name. # For example, if the log file is named production.log, the first time it is rolled it will be renamed # production.log.1, then production.log.2, etc. class SizeRollingLogFile < RollingLogFile attr_reader :max_size # Create an new log device to the specified file. The maximum size of the log file is specified with # the :max_size option. The unit can also be specified: "32K", "100M", "2G" are all valid. def initialize(path, options = {}) @manual = options[:manual] @max_size = options[:max_size] if @max_size.is_a?(String) if @max_size.match(/^(\d+(\.\d+)?)([KMG])?$/i) @max_size = $~[1].to_f units = $~[3].to_s.upcase case units when "K" @max_size *= 1024 when "M" @max_size *= 1024 ** 2 when "G" @max_size *= 1024 ** 3 end @max_size = @max_size.round else raise ArgumentError.new("illegal value for :max_size (#{@max_size})") end end super end def archive_file_suffix next_archive_number.to_s end def roll_file? @manual || stream.stat.size > @max_size rescue SystemCallError false end protected # Calculate the next archive file name extension. def next_archive_number # :nodoc: max = 0 Dir.glob("#{path}.*").each do |filename| if filename.match(/\.\d+$/) suffix = filename.split('.').last.to_i max = suffix if suffix > max end end max + 1 end end end end lumberjack-1.0.13/lib/lumberjack/device/writer.rb0000644000004100000410000001202013260145344021725 0ustar www-datawww-data# frozen_string_literals: true module Lumberjack class Device # This logging device writes log entries as strings to an IO stream. By default, messages will be buffered # and written to the stream in a batch when the buffer is full or when +flush+ is called. class Writer < Device DEFAULT_FIRST_LINE_TEMPLATE = "[:time :severity :progname(:pid) #:unit_of_work_id] :message".freeze DEFAULT_ADDITIONAL_LINES_TEMPLATE = "#{Lumberjack::LINE_SEPARATOR}> [#:unit_of_work_id] :message".freeze # The size of the internal buffer. Defaults to 32K. attr_reader :buffer_size # Internal buffer to batch writes to the stream. class Buffer # :nodoc: attr_reader :size def initialize @values = [] @size = 0 end def <<(string) @values << string @size += string.size end def empty? @values.empty? end def pop! popped = @values clear popped end def clear @values = [] @size = 0 end end # Create a new device to write log entries to a stream. Entries are converted to strings # using a Template. The template can be specified using the :template option. This can # either be a Proc or a string that will compile into a Template object. # # If the template is a Proc, it should accept an LogEntry as its only argument and output a string. # # If the template is a template string, it will be used to create a Template. The # :additional_lines and :time_format options will be passed through to the # Template constuctor. # # The default template is "[:time :severity :progname(:pid) #:unit_of_work_id] :message" # with additional lines formatted as "\n [#:unit_of_work_id] :message". The unit of # work id will only appear if it is present. # # The size of the internal buffer in bytes can be set by providing :buffer_size (defaults to 32K). def initialize(stream, options = {}) @lock = Mutex.new @stream = stream @stream.sync = true if @stream.respond_to?(:sync=) @buffer = Buffer.new @buffer_size = (options[:buffer_size] || 0) template = (options[:template] || DEFAULT_FIRST_LINE_TEMPLATE) if template.respond_to?(:call) @template = template else additional_lines = (options[:additional_lines] || DEFAULT_ADDITIONAL_LINES_TEMPLATE) @template = Template.new(template, :additional_lines => additional_lines, :time_format => options[:time_format]) end end # Set the buffer size in bytes. The device will only be physically written to when the buffer size # is exceeded. def buffer_size=(value) @buffer_size = value flush end # Write an entry to the stream. The entry will be converted into a string using the defined template. def write(entry) string = @template.call(entry) unless string.nil? string = string.encode("UTF-8".freeze, invalid: :replace, undef: :replace) @lock.synchronize do @buffer << string end end flush if @buffer.size >= buffer_size end # Close the underlying stream. def close flush stream.close end # Flush the underlying stream. def flush lines = nil @lock.synchronize do before_flush lines = @buffer.pop! end unless lines.empty? out = "#{lines.join(Lumberjack::LINE_SEPARATOR)}#{Lumberjack::LINE_SEPARATOR}" begin begin stream.write(out) rescue IOError => e # This condition can happen if another thread closed the stream in the `before_flush` call. # Synchronizing will handle the race condition, but since it's an exceptional case we don't # to lock the thread on every stream write call. @lock.synchronize do if stream.closed? raise e else stream.write(out) end end end stream.flush rescue nil rescue => e $stderr.write("#{e.class.name}: #{e.message}#{' at ' + e.backtrace.first if e.backtrace}") $stderr.write(out) $stderr.flush end end end protected # Callback method that will be executed before data is written to the stream. Subclasses # can override this method if needed. This method will be called in a mutex lock. def before_flush end # Set the underlying stream. def stream=(stream) @stream = stream end # Get the underlying stream. def stream @stream end end end end lumberjack-1.0.13/lib/lumberjack/device/rolling_log_file.rb0000644000004100000410000001025613260145344023730 0ustar www-datawww-data# frozen_string_literals: true module Lumberjack class Device # This is an abstract class for a device that appends entries to a file and periodically archives # the existing file and starts a one. Subclasses must implement the roll_file? and archive_file_suffix # methods. # # The :keep option can be used to specify a maximum number of rolled log files to keep. # Older files will be deleted based on the time they were created. The default is to keep all files. # # The :min_roll_check option can be used to specify the number of seconds between checking # the file to determine if it needs to be rolled. The default is to check at most once per second. class RollingLogFile < LogFile attr_reader :path attr_accessor :keep def initialize(path, options = {}) @path = File.expand_path(path) @keep = options[:keep] super(path, options) @file_inode = stream.lstat.ino rescue nil @@rolls = [] @next_stat_check = Time.now.to_f @min_roll_check = (options[:min_roll_check] || 1.0).to_f end # Returns a suffix that will be appended to the file name when it is archived.. The suffix should # change after it is time to roll the file. The log file will be renamed when it is rolled. def archive_file_suffix raise NotImplementedError end # Return +true+ if the file should be rolled. def roll_file? raise NotImplementedError end # Roll the log file by renaming it to the archive file name and then re-opening a stream to the log # file path. Rolling a file is safe in multi-threaded or multi-process environments. def roll_file! #:nodoc: do_once(stream) do archive_file = "#{path}.#{archive_file_suffix}" stream.flush current_inode = File.stat(path).ino rescue nil if @file_inode && current_inode == @file_inode && !File.exist?(archive_file) && File.exist?(path) begin File.rename(path, archive_file) after_roll cleanup_files! rescue SystemCallError # Ignore rename errors since it indicates the file was already rolled end end reopen_file end rescue => e $stderr.write("Failed to roll file #{path}: #{e.inspect}\n#{e.backtrace.join("\n")}\n") end protected # This method will be called after a file has been rolled. Subclasses can # implement code to reset the state of the device. This method is thread safe. def after_roll end # Handle rolling the file before flushing. def before_flush # :nodoc: if @min_roll_check <= 0.0 || Time.now.to_f >= @next_stat_check @next_stat_check += @min_roll_check path_inode = File.lstat(path).ino rescue nil if path_inode != @file_inode @file_inode = path_inode reopen_file else roll_file! if roll_file? end end end private def reopen_file old_stream = stream new_stream = File.open(path, 'a', encoding: EXTERNAL_ENCODING) new_stream.sync = true if buffer_size > 0 @file_inode = new_stream.lstat.ino rescue nil self.stream = new_stream old_stream.close end end def cleanup_files! if keep files = Dir.glob("#{path}.*").collect{|f| [f, File.ctime(f)]}.sort{|a,b| b.last <=> a.last}.collect{|a| a.first} if files.size > keep files[keep, files.length].each do |f| File.delete(f) end end end end def do_once(file) begin file.flock(File::LOCK_EX) rescue SystemCallError # Most likely can't lock file because the stream is closed return end begin verify = file.lstat rescue nil # Execute only if the file we locked is still the same one that needed to be rolled yield if verify && verify.ino == @file_inode && verify.size > 0 ensure file.flock(File::LOCK_UN) rescue nil end end end end lumberjack-1.0.13/lib/lumberjack/device/null.rb0000644000004100000410000000052413260145344021371 0ustar www-datawww-data# frozen_string_literals: true require 'date' module Lumberjack class Device # This is a logging device that produces no output. It can be useful in # testing environments when log file output is not useful. class Null < Device def initialize(*args) end def write(entry) end end end end lumberjack-1.0.13/lib/lumberjack/device/date_rolling_log_file.rb0000644000004100000410000000413313260145344024722 0ustar www-datawww-data# frozen_string_literals: true require 'date' module Lumberjack class Device # This log device will append entries to a file and roll the file periodically by date. Files # are rolled at midnight and can be rolled daily, weekly, or monthly. Archive file names will # have the date appended to them in the format ".YYYY-MM-DD" for daily, ".week-of-YYYY-MM-DD" for weekly # and ".YYYY-MM" for monthly. It is not guaranteed that log messages will break exactly on the # roll period as buffered entries will always be written to the same file. class DateRollingLogFile < RollingLogFile # Create a new logging device to the specified file. The period to roll the file is specified # with the :roll option which may contain a value of :daily, :weekly, # or :monthly. def initialize(path, options = {}) @manual = options[:manual] @file_date = Date.today if options[:roll] && options[:roll].to_s.match(/(daily)|(weekly)|(monthly)/i) @roll_period = $~[0].downcase.to_sym options.delete(:roll) else raise ArgumentError.new("illegal value for :roll (#{options[:roll].inspect})") end super end def archive_file_suffix case @roll_period when :weekly "#{@file_date.strftime('week-of-%Y-%m-%d')}" when :monthly "#{@file_date.strftime('%Y-%m')}" else "#{@file_date.strftime('%Y-%m-%d')}" end end def roll_file? if @manual true else date = Date.today if date.year > @file_date.year true elsif @roll_period == :daily && date.yday > @file_date.yday true elsif @roll_period == :weekly && date.cweek != @file_date.cweek true elsif @roll_period == :monthly && date.month > @file_date.month true else false end end end protected def after_roll @file_date = Date.today end end end end lumberjack-1.0.13/lib/lumberjack/template.rb0000644000004100000410000000616013260145344020775 0ustar www-datawww-data# frozen_string_literals: true module Lumberjack # A template converts entries to strings. Templates can contain the following place holders to # reference log entry values: # # * :time # * :severity # * :progname # * :unit_of_work_id # * :message class Template TEMPLATE_ARGUMENT_ORDER = %w(:time :severity :progname :pid :unit_of_work_id :message).freeze DEFAULT_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S." MILLISECOND_FORMAT = "%03d" MICROSECOND_FORMAT = "%06d" # Create a new template from the markup. The +first_line+ argument is used to format only the first # line of a message. Additional lines will be added to the message unformatted. If you wish to format # the additional lines, use the :additional_lines options to specify a template. Note that you'll need # to provide the line separator character in this template if you want to keep the message on multiple lines. # # The time will be formatted as YYYY-MM-DDTHH:MM:SSS.SSS by default. If you wish to change the format, you # can specify the :time_format option which can be either a time format template as documented in # +Time#strftime+ or the values +:milliseconds+ or +:microseconds+ to use the standard format with the # specified precision. # # Messages will have white space stripped from both ends. def initialize(first_line, options = {}) @first_line_template = compile(first_line) additional_lines = options[:additional_lines] || "#{Lumberjack::LINE_SEPARATOR}:message" @additional_line_template = compile(additional_lines) # Formatting the time is relatively expensive, so only do it if it will be used @template_include_time = first_line.include?(":time") || additional_lines.include?(":time") @time_format = options[:time_format] || :milliseconds end # Convert an entry into a string using the template. def call(entry) lines = entry.message.strip.split(Lumberjack::LINE_SEPARATOR) formatted_time = format_time(entry.time) if @template_include_time message = @first_line_template % [formatted_time, entry.severity_label, entry.progname, entry.pid, entry.unit_of_work_id, lines.shift] lines.each do |line| message << @additional_line_template % [formatted_time, entry.severity_label, entry.progname, entry.pid, entry.unit_of_work_id, line] end message end private def format_time(time) #:nodoc: if @time_format.is_a?(String) time.strftime(@time_format) elsif @time_format == :milliseconds time.strftime(DEFAULT_TIME_FORMAT) << MILLISECOND_FORMAT % (time.usec / 1000.0).round else time.strftime(DEFAULT_TIME_FORMAT) << MICROSECOND_FORMAT % time.usec end end # Compile the template string into a value that can be used with sprintf. def compile(template) #:nodoc: template.gsub(/:[a-z0-9_]+/) do |match| position = TEMPLATE_ARGUMENT_ORDER.index(match) if position "%#{position + 1}$s" else match end end end end end lumberjack-1.0.13/lumberjack.gemspec0000644000004100000410000000562513260145344017441 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- Gem::Specification.new do |s| s.name = "lumberjack" s.version = "1.0.13" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Brian Durand"] s.date = "2018-03-29" s.description = "A simple, powerful, and very fast logging utility that can be a drop in replacement for Logger or ActiveSupport::BufferedLogger. Provides support for automatically rolling log files even with multiple processes writing the same log file." s.email = ["bbdurand@gmail.com"] s.files = ["MIT_LICENSE", "README.md", "Rakefile", "VERSION", "lib/lumberjack.rb", "lib/lumberjack/device.rb", "lib/lumberjack/device/date_rolling_log_file.rb", "lib/lumberjack/device/log_file.rb", "lib/lumberjack/device/null.rb", "lib/lumberjack/device/rolling_log_file.rb", "lib/lumberjack/device/size_rolling_log_file.rb", "lib/lumberjack/device/writer.rb", "lib/lumberjack/formatter.rb", "lib/lumberjack/formatter/exception_formatter.rb", "lib/lumberjack/formatter/inspect_formatter.rb", "lib/lumberjack/formatter/pretty_print_formatter.rb", "lib/lumberjack/formatter/string_formatter.rb", "lib/lumberjack/log_entry.rb", "lib/lumberjack/logger.rb", "lib/lumberjack/rack.rb", "lib/lumberjack/rack/request_id.rb", "lib/lumberjack/rack/unit_of_work.rb", "lib/lumberjack/severity.rb", "lib/lumberjack/template.rb", "spec/device/date_rolling_log_file_spec.rb", "spec/device/log_file_spec.rb", "spec/device/null_spec.rb", "spec/device/rolling_log_file_spec.rb", "spec/device/size_rolling_log_file_spec.rb", "spec/device/writer_spec.rb", "spec/formatter/exception_formatter_spec.rb", "spec/formatter/inspect_formatter_spec.rb", "spec/formatter/pretty_print_formatter_spec.rb", "spec/formatter/string_formatter_spec.rb", "spec/formatter_spec.rb", "spec/log_entry_spec.rb", "spec/logger_spec.rb", "spec/lumberjack_spec.rb", "spec/rack/request_id_spec.rb", "spec/rack/unit_of_work_spec.rb", "spec/severity_spec.rb", "spec/spec_helper.rb", "spec/template_spec.rb"] s.homepage = "https://github.com/bdurand/lumberjack" s.licenses = ["MIT"] s.require_paths = ["lib"] s.rubygems_version = "1.8.23" s.summary = "A simple, powerful, and very fast logging utility that can be a drop in replacement for Logger or ActiveSupport::BufferedLogger." if s.respond_to? :specification_version then s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_development_dependency(%q, ["~> 3.0"]) s.add_development_dependency(%q, ["~> 0.8"]) else s.add_dependency(%q, ["~> 3.0"]) s.add_dependency(%q, ["~> 0.8"]) end else s.add_dependency(%q, ["~> 3.0"]) s.add_dependency(%q, ["~> 0.8"]) end end lumberjack-1.0.13/VERSION0000644000004100000410000000000713260145344015012 0ustar www-datawww-data1.0.13 lumberjack-1.0.13/README.md0000644000004100000410000002036613260145344015233 0ustar www-datawww-data# Lumberjack Lumberjack is a simple, powerful, and fast logging implementation in Ruby. It uses nearly the same API as the Logger class in the Ruby standard library and as ActiveSupport::BufferedLogger in Rails. ## Usage This code aims to be extremely simple to use. The core interface it the Lumberjack::Logger which is used to log messages (which can be any object) with a specified Severity. Each logger has a level associated with it and messages are only written if their severity is greater than or equal to the level. ```ruby logger = Lumberjack::Logger.new("logs/application.log") # Open a new log file with INFO level logger.info("Begin request") logger.debug(request.params) # Message not written unless the level is set to DEBUG begin # do something rescue => exception logger.error(exception) raise end logger.info("End request") ``` This is all you need to know to log messages. ## Features ### Meta data When messages are added to the log, additional data about the message is kept in a Lumberjack::LogEntry. This means you don't need to worry about adding the time or process id to your log messages as they will be automatically recorded. The following information is recorded for each message: * severity - The severity recorded for the message. * time - The time at which the message was recorded. * program name - The name of the program logging the message. This can be either set for all messages or customized with each message. * process id - The process id (pid) of the process that logged the message. * unit of work id - The unique 12 byte hexadecimal number generated for a unit of work. ### Units Of Work A unit of work can be used to tie together all log messages within a block. This can be very useful to isolate a group of messages that represent one path through the system. For instance, in a web application, a single request represents a natural unit of work, and when you are looking through a log file, it is useful to see the entire set of log entries as a unit instead of interspersed with messages from other concurrent requests. ```ruby # All log entries in this block will get a common unit of work id. Lumberjack.unit_of_work do logger.info("Begin request") yield logger.info("End request") end ``` ### Pluggable Devices When a Logger logs a LogEntry, it sends it to a Lumberjack::Device. Lumberjack comes with a variety of devices for logging to IO streams or files. * Lumberjack::Device::Writer - Writes log entries to an IO stream. * Lumberjack::Device::LogFile - Writes log entries to a file. * Lumberjack::Device::DateRollingLogFile - Writes log entries to a file that will automatically roll itself based on date. * Lumberjack::Device::SizeRollingLogFile - Writes log entries to a file that will automatically roll itself based on size. * Lumberjack::Device::Null - This device produces no output and is intended for testing environments. If you'd like to send you log to a different kind of output, you just need to extend the Device class and implement the +write+ method. Or check out these plugins: * [lumberjack_syslog_device](https://github.com/bdurand/lumberjack_syslog_device) - send your log messages to the system wide syslog service * [lumberjack_multi-device](https://github.com/astevens/lumberjack_multi-device) - send log messages to multiple devices * [lumberjack_mongo_device](https://github.com/bdurand/lumberjack_mongo_device) - store your log messages to a [MongoDB](http://www.mongodb.org/) NoSQL data store * [lumberjack-couchdb-driver](https://github.com/narkisr/lumberjack-couchdb-driver) - store your log messages to a [CouchDB](http://couchdb.apache.org/) NoSQL data store * [lumberjack_heroku_device](https://github.com/tonycoco/lumberjack_heroku_device) - log to Heroku's logging system ### Customize Formatting When a message is logged, it is first converted into a string. You can customize how it is converted by adding mappings to a Formatter. ```ruby logger.formatter.add(Hash, :pretty_print) # use the Formatter::PrettyPrintFormatter for all Hashes logger.formatter.add(MyClass){|obj| "#{obj.class}@#{obj.id}"} # use a block to provide a custom format ``` If you use the built in devices, you can also customize the Template used to format the LogEntry. ```ruby # Change the format of the time in the log Lumberjack::Logger.new("application.log", :time_format => "%m/%d/%Y %H:%M:%S") # Use a simple template that only includes the time and the message Lumberjack::Logger.new("application.log", :template => ":time - :message") # Use a custom template as a block that only includes the first character of the severity template = lambda{|e| "#{e.severity_label[0, 1]} #{e.time} - #{e.message}"} Lumberjack::Logger.new("application.log", :template => template) ``` ### Buffered Performance The logger has hooks for devices that support buffering to increase performance by batching physical writes. Log entries are not guaranteed to be written until the Lumberjack::Logger#flush method is called. You can use the :flush_seconds option on the logger to periodically flush the log. This is usually a good idea so you can more easily debug hung processes. Without periodic flushing, a process that hangs may never write anything to the log because the messages are sitting in a buffer. By turning on periodic flushing, the logged messages will be written which can greatly aid in debugging the problem. The built in stream based logging devices use an internal buffer. The size of the buffer (in bytes) can be set with the :buffer_size options when initializing a logger. The default behavior is to not to buffer. ```ruby # Set buffer to flush after 8K has been written to the log. logger = Lumberjack::Logger.new("application.log", :buffer_size => 8192) # Turn off buffering so entries are immediately written to disk. logger = Lumberjack::Logger.new("application.log", :buffer_size => 0) ``` ### Automatic Log Rolling The built in devices include two that can automatically roll log files based either on date or on file size. When a log file is rolled, it will be renamed with a suffix and a new file will be created to receive new log entries. This can keep your log files from growing to unusable sizes and removes the need to schedule an external process to roll the files. There is a similar feature in the standard library Logger class, but the implementation here is safe to use with multiple processes writing to the same log file. ## Examples These example are for Rails applications, but there is no dependency on Rails for using this gem. Most of the examples are applicable to any Ruby application. In a Rails application you can replace the default production logger by adding this to your config/environments/production.rb file: ```ruby # Use the ActionDispatch request id as the unit of work id. This will use just the first chunk of the request id. # If you want to use an abbreviated request id for terseness, change the last argument to `true` config.middleware.insert_after ActionDispatch::RequestId, Lumberjack::Rack::RequestId, false # Use a custom unit of work id to each request # config.middleware.insert(0, Lumberjack::Rack::UnitOfWork) # Change the logger to use Lumberjack log_file_path = Rails.root + "log" + "#{Rails.env}.log" config.logger = Lumberjack::Logger.new(log_file, :level => :warn) ``` To set up a logger to roll every day at midnight, you could use this code (you can also specify :weekly or :monthly): ```ruby config.logger = Lumberjack::Logger.new(log_file_path, :roll => :daily) ``` To set up a logger to roll log files when they get to 100Mb, you could use this: ```ruby config.logger = Lumberjack::Logger.new(log_file_path, :max_size => 100.megabytes) ``` To change the log message format, you could use this code: ```ruby config.logger = Lumberjack::Logger.new(log_file_path, :template => ":time - :message") ``` To change the log message format to output JSON, you could use this code (this example requires the multi-json gem): ```ruby config.logger = Lumberjack::Logger.new(log_file_path, :template => lambda{|e| MultiJson.dump(e)}) ``` To send log messages to syslog instead of to a file, you could use this (require the lumberjack_syslog_device gem): ```ruby config.logger = Lumberjack::Logger.new(Lumberjack::SyslogDevice.new) ```