mono_logger-1.1.1/0000755000004100000410000000000014052722607014056 5ustar www-datawww-datamono_logger-1.1.1/.travis.yml0000644000004100000410000000006314052722607016166 0ustar www-datawww-datalanguage: ruby rvm: - 2.0.0 - jruby-19mode mono_logger-1.1.1/test/0000755000004100000410000000000014052722607015035 5ustar www-datawww-datamono_logger-1.1.1/test/mri_logger_test.rb0000644000004100000410000003560314052722607020556 0ustar www-datawww-data# coding: US-ASCII require 'simplecov' SimpleCov.start do add_filter do |source_file| source_file.filename =~ /test/ end end require 'coveralls' Coveralls.wear! require 'minitest/autorun' require 'mono_logger' # Logger::Application was dropped at Ruby 2.2. require 'logger-application' unless defined?(Logger::Application) require 'tempfile' if defined? Minitest::Test # We're on Minitest 5+. Nothing to do here. else # Minitest 4 doesn't have Minitest::Test yet. Minitest::Test = MiniTest::Unit::TestCase end Logger = MonoLogger class TestLoggerSeverity < Minitest::Test def test_enum logger_levels = Logger.constants levels = ["WARN", "UNKNOWN", "INFO", "FATAL", "DEBUG", "ERROR"] Logger::Severity.constants.each do |level| assert(levels.include?(level.to_s)) assert(logger_levels.include?(level)) end assert_equal(levels.size, Logger::Severity.constants.size) end end class TestLogger < Minitest::Test include Logger::Severity def setup @logger = Logger.new(nil) end class Log attr_reader :label, :datetime, :pid, :severity, :progname, :msg def initialize(line) /\A(\w+), \[([^#]*)#(\d+)\]\s+(\w+) -- (\w*): ([\x0-\xff]*)/ =~ line @label, @datetime, @pid, @severity, @progname, @msg = $1, $2, $3, $4, $5, $6 end end def log_add(logger, severity, msg, progname = nil, &block) log(logger, :add, severity, msg, progname, &block) end def log(logger, msg_id, *arg, &block) Log.new(log_raw(logger, msg_id, *arg, &block)) end def log_raw(logger, msg_id, *arg, &block) logdev = Tempfile.new(File.basename(__FILE__) + '.log') logger.instance_eval { @logdev = Logger::LogDevice.new(logdev) } logger.__send__(msg_id, *arg, &block) logdev.open msg = logdev.read logdev.close msg end def test_level @logger.level = UNKNOWN assert_equal(UNKNOWN, @logger.level) @logger.level = INFO assert_equal(INFO, @logger.level) @logger.sev_threshold = ERROR assert_equal(ERROR, @logger.sev_threshold) @logger.sev_threshold = WARN assert_equal(WARN, @logger.sev_threshold) assert_equal(WARN, @logger.level) @logger.level = DEBUG assert(@logger.debug?) assert(@logger.info?) @logger.level = INFO assert(!@logger.debug?) assert(@logger.info?) assert(@logger.warn?) @logger.level = WARN assert(!@logger.info?) assert(@logger.warn?) assert(@logger.error?) @logger.level = ERROR assert(!@logger.warn?) assert(@logger.error?) assert(@logger.fatal?) @logger.level = FATAL assert(!@logger.error?) assert(@logger.fatal?) @logger.level = UNKNOWN assert(!@logger.error?) assert(!@logger.fatal?) end def test_progname assert_nil(@logger.progname) @logger.progname = "name" assert_equal("name", @logger.progname) end def test_datetime_format dummy = STDERR logger = Logger.new(dummy) log = log_add(logger, INFO, "foo") assert_match(/^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d.\s*\d+ $/, log.datetime) logger.datetime_format = "%d%b%Y@%H:%M:%S" log = log_add(logger, INFO, "foo") assert_match(/^\d\d\w\w\w\d\d\d\d@\d\d:\d\d:\d\d$/, log.datetime) logger.datetime_format = "" log = log_add(logger, INFO, "foo") assert_match(/^$/, log.datetime) end def test_formatter dummy = STDERR logger = Logger.new(dummy) # default log = log(logger, :info, "foo") assert_equal("foo\n", log.msg) # config logger.formatter = proc { |severity, timestamp, progname, msg| "#{severity}:#{msg}\n\n" } line = log_raw(logger, :info, "foo") assert_equal("INFO:foo\n\n", line) # recover logger.formatter = nil log = log(logger, :info, "foo") assert_equal("foo\n", log.msg) # again o = Object.new def o.call(severity, timestamp, progname, msg) "<<#{severity}-#{msg}>>\n" end logger.formatter = o line = log_raw(logger, :info, "foo") assert_equal("<"">\n", line) end def test_initialize logger = Logger.new(STDERR) assert_nil(logger.progname) assert_equal(DEBUG, logger.level) assert_nil(logger.datetime_format) end def test_add logger = Logger.new(nil) logger.progname = "my_progname" assert(logger.add(INFO)) log = log_add(logger, nil, "msg") assert_equal("ANY", log.severity) assert_equal("my_progname", log.progname) logger.level = WARN assert(logger.log(INFO)) assert_nil(log_add(logger, INFO, "msg").msg) log = log_add(logger, WARN, nil) { "msg" } assert_equal("msg\n", log.msg) log = log_add(logger, WARN, "") { "msg" } assert_equal("\n", log.msg) assert_equal("my_progname", log.progname) log = log_add(logger, WARN, nil, "progname?") assert_equal("progname?\n", log.msg) assert_equal("my_progname", log.progname) end def test_level_log logger = Logger.new(nil) logger.progname = "my_progname" log = log(logger, :debug, "custom_progname") { "msg" } assert_equal("msg\n", log.msg) assert_equal("custom_progname", log.progname) assert_equal("DEBUG", log.severity) assert_equal("D", log.label) # log = log(logger, :debug) { "msg_block" } assert_equal("msg_block\n", log.msg) assert_equal("my_progname", log.progname) log = log(logger, :debug, "msg_inline") assert_equal("msg_inline\n", log.msg) assert_equal("my_progname", log.progname) # log = log(logger, :info, "custom_progname") { "msg" } assert_equal("msg\n", log.msg) assert_equal("custom_progname", log.progname) assert_equal("INFO", log.severity) assert_equal("I", log.label) # log = log(logger, :warn, "custom_progname") { "msg" } assert_equal("msg\n", log.msg) assert_equal("custom_progname", log.progname) assert_equal("WARN", log.severity) assert_equal("W", log.label) # log = log(logger, :error, "custom_progname") { "msg" } assert_equal("msg\n", log.msg) assert_equal("custom_progname", log.progname) assert_equal("ERROR", log.severity) assert_equal("E", log.label) # log = log(logger, :fatal, "custom_progname") { "msg" } assert_equal("msg\n", log.msg) assert_equal("custom_progname", log.progname) assert_equal("FATAL", log.severity) assert_equal("F", log.label) # log = log(logger, :unknown, "custom_progname") { "msg" } assert_equal("msg\n", log.msg) assert_equal("custom_progname", log.progname) assert_equal("ANY", log.severity) assert_equal("A", log.label) end def test_close r, w = IO.pipe assert(!w.closed?) logger = Logger.new(w) logger.close assert(w.closed?) r.close end class MyError < StandardError end class MyMsg def inspect "my_msg" end end def test_format logger = Logger.new(nil) log = log_add(logger, INFO, "msg\n") assert_equal("msg\n\n", log.msg) begin raise MyError.new("excn") rescue MyError => e log = log_add(logger, INFO, e) assert_match(/^excn \(TestLogger::MyError\)/, log.msg) # expects backtrace is dumped across multi lines. 10 might be changed. assert(log.msg.split(/\n/).size >= 10) end log = log_add(logger, INFO, MyMsg.new) assert_equal("my_msg\n", log.msg) end def test_lshift r, w = IO.pipe logger = Logger.new(w) logger << "msg" _, = IO.select([r], nil, nil, 0.1) w.close msg = r.read r.close assert_equal("msg", msg) # r, w = IO.pipe logger = Logger.new(w) logger << "msg2\n\n" _, = IO.select([r], nil, nil, 0.1) w.close msg = r.read r.close assert_equal("msg2\n\n", msg) end end class TestLogDevice < Minitest::Test class LogExcnRaiser def write(*arg) raise 'disk is full' end def close end def stat Object.new end end def setup @tempfile = Tempfile.new("logger") @tempfile.close @filename = @tempfile.path File.unlink(@filename) end def teardown @tempfile.close(true) end def d(log, opt = {}) Logger::LogDevice.new(log, **opt) end def test_initialize logdev = d(STDERR) assert_equal(STDERR, logdev.dev) assert_nil(logdev.filename) assert_raises(TypeError) do d(nil) end # logdev = d(@filename) begin assert(File.exist?(@filename)) assert(logdev.dev.sync) assert_equal(@filename, logdev.filename) logdev.write('hello') ensure logdev.close end # create logfile whitch is already exist. logdev = d(@filename) begin logdev.write('world') logfile = File.read(@filename) assert_equal(2, logfile.split(/\n/).size) assert_match(/^helloworld$/, logfile) ensure logdev.close end end def test_write r, w = IO.pipe logdev = d(w) logdev.write("msg2\n\n") _, = IO.select([r], nil, nil, 0.1) w.close msg = r.read r.close assert_equal("msg2\n\n", msg) # logdev = d(LogExcnRaiser.new) class << (stderr = '') alias write << end $stderr, stderr = stderr, $stderr begin logdev.write('hello') ensure logdev.close $stderr, stderr = stderr, $stderr end assert_equal "log writing failed. disk is full\n", stderr end def test_close r, w = IO.pipe logdev = d(w) logdev.write("msg2\n\n") _, = IO.select([r], nil, nil, 0.1) assert(!w.closed?) logdev.close assert(w.closed?) r.close end def test_shifting_size skip("shifting age doesn't work because rotation doesn't work") tmpfile = Tempfile.new([File.basename(__FILE__, '.*'), '_1.log']) logfile = tmpfile.path logfile0 = logfile + '.0' logfile1 = logfile + '.1' logfile2 = logfile + '.2' logfile3 = logfile + '.3' tmpfile.close(true) File.unlink(logfile) if File.exist?(logfile) File.unlink(logfile0) if File.exist?(logfile0) File.unlink(logfile1) if File.exist?(logfile1) File.unlink(logfile2) if File.exist?(logfile2) logger = Logger.new(logfile, 4, 100) logger.error("0" * 15) assert(File.exist?(logfile)) assert(!File.exist?(logfile0)) logger.error("0" * 15) assert(File.exist?(logfile0)) assert(!File.exist?(logfile1)) logger.error("0" * 15) assert(File.exist?(logfile1)) assert(!File.exist?(logfile2)) logger.error("0" * 15) assert(File.exist?(logfile2)) assert(!File.exist?(logfile3)) logger.error("0" * 15) assert(!File.exist?(logfile3)) logger.error("0" * 15) assert(!File.exist?(logfile3)) logger.close File.unlink(logfile) File.unlink(logfile0) File.unlink(logfile1) File.unlink(logfile2) tmpfile = Tempfile.new([File.basename(__FILE__, '.*'), '_2.log']) logfile = tmpfile.path logfile0 = logfile + '.0' logfile1 = logfile + '.1' logfile2 = logfile + '.2' logfile3 = logfile + '.3' tmpfile.close(true) logger = Logger.new(logfile, 4, 150) logger.error("0" * 15) assert(File.exist?(logfile)) assert(!File.exist?(logfile0)) logger.error("0" * 15) assert(!File.exist?(logfile0)) logger.error("0" * 15) assert(File.exist?(logfile0)) assert(!File.exist?(logfile1)) logger.error("0" * 15) assert(!File.exist?(logfile1)) logger.error("0" * 15) assert(File.exist?(logfile1)) assert(!File.exist?(logfile2)) logger.error("0" * 15) assert(!File.exist?(logfile2)) logger.error("0" * 15) assert(File.exist?(logfile2)) assert(!File.exist?(logfile3)) logger.error("0" * 15) assert(!File.exist?(logfile3)) logger.error("0" * 15) assert(!File.exist?(logfile3)) logger.error("0" * 15) assert(!File.exist?(logfile3)) logger.close File.unlink(logfile) File.unlink(logfile0) File.unlink(logfile1) File.unlink(logfile2) end def test_shifting_age_variants skip("shifting age doesn't work because rotation doesn't work") logger = Logger.new(@filename, 'daily') logger.info('daily') logger.close logger = Logger.new(@filename, 'weekly') logger.info('weekly') logger.close logger = Logger.new(@filename, 'monthly') logger.info('monthly') logger.close end def test_shifting_age skip("shifting age doesn't work because rotation doesn't work") # shift_age other than 'daily', 'weekly', and 'monthly' means 'everytime' yyyymmdd = Time.now.strftime("%Y%m%d") filename1 = @filename + ".#{yyyymmdd}" filename2 = @filename + ".#{yyyymmdd}.1" filename3 = @filename + ".#{yyyymmdd}.2" begin logger = Logger.new(@filename, 'now') assert(File.exist?(@filename)) assert(!File.exist?(filename1)) assert(!File.exist?(filename2)) assert(!File.exist?(filename3)) logger.info("0" * 15) assert(File.exist?(@filename)) assert(File.exist?(filename1)) assert(!File.exist?(filename2)) assert(!File.exist?(filename3)) logger.warn("0" * 15) assert(File.exist?(@filename)) assert(File.exist?(filename1)) assert(File.exist?(filename2)) assert(!File.exist?(filename3)) logger.error("0" * 15) assert(File.exist?(@filename)) assert(File.exist?(filename1)) assert(File.exist?(filename2)) assert(File.exist?(filename3)) ensure logger.close if logger [filename1, filename2, filename3].each do |filename| File.unlink(filename) if File.exist?(filename) end end end end class TestLoggerApplication < Minitest::Test def setup @app = Logger::Application.new('appname') @tempfile = Tempfile.new("logger") @tempfile.close @filename = @tempfile.path File.unlink(@filename) end def teardown @tempfile.close(true) end def test_initialize app = Logger::Application.new('appname') assert_equal('appname', app.appname) end def test_start @app.set_log(@filename) begin @app.level = Logger::UNKNOWN @app.start # logs FATAL log assert_equal(1, File.read(@filename).split(/\n/).size) ensure @app.logger.close end end def test_logger @app.level = Logger::WARN @app.set_log(@filename) begin assert_equal(Logger::WARN, @app.logger.level) ensure @app.logger.close end @app.logger = logger = Logger.new(STDOUT) assert_equal(logger, @app.logger) assert_equal(Logger::WARN, @app.logger.level) @app.log = @filename begin assert(logger != @app.logger) assert_equal(Logger::WARN, @app.logger.level) ensure @app.logger.close end end end mono_logger-1.1.1/mono_logger.gemspec0000644000004100000410000000204214052722607017730 0ustar www-datawww-data# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'mono_logger/version' Gem::Specification.new do |spec| spec.name = "mono_logger" spec.version = MonoLogger::VERSION spec.authors = ["Steve Klabnik"] spec.email = ["steve@steveklabnik.com"] spec.description = %q{A lock-free logger compatible with Ruby 2.0. Ruby does not allow you to request a lock in a trap handler because that could deadlock, so Logger is not sufficient.} spec.summary = %q{A lock-free logger compatible with Ruby 2.0.} spec.homepage = "http://github.com/steveklabnik/mono_logger" spec.license = "MIT" spec.files = `git ls-files`.split($/) spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ["lib"] spec.add_development_dependency "rake" spec.add_development_dependency "minitest", "~> 5.0" end mono_logger-1.1.1/README.md0000644000004100000410000000333014052722607015334 0ustar www-datawww-data# MonoLogger [![Build Status](https://travis-ci.org/steveklabnik/mono_logger.png?branch=master)](https://travis-ci.org/steveklabnik/mono_logger) [![Code Climate](https://codeclimate.com/github/steveklabnik/mono_logger.png)](https://codeclimate.com/github/steveklabnik/mono_logger) [![Coverage Status](https://coveralls.io/repos/steveklabnik/mono_logger/badge.png)](https://coveralls.io/r/steveklabnik/mono_logger) Ruby's stdlib Logger wraps all IO in mutexes. Ruby 2.0 doesn't allow you to request a lock in a trap handler because that could deadlock. This gem fixes this issue by giving you a lock-free logger class. If you've ever seen `log writing failed. can't be called from trap context`, you're in the right place! ## Installation Add this line to your application's Gemfile: gem 'mono_logger' And then execute: $ bundle Or install it yourself as: $ gem install mono_logger ## Usage It's simple, just use `MonoLogger` anywhere you'd use `Logger`: ```ruby require 'logger' logger = Logger.new(STDOUT) logger.level = Logger::WARN logger.debug("Created logger") logger.info("Program started") logger.warn("Nothing to do!") ``` Turns into ```ruby require 'mono_logger' logger = MonoLogger.new(STDOUT) logger.level = MonoLogger::WARN logger.debug("Created logger") logger.info("Program started") logger.warn("Nothing to do!") ``` That's it! No more errors! ## Contributing 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request ## License MIT. See LICENSE.txt for more details. mono_logger-1.1.1/.gitignore0000644000004100000410000000025314052722607016046 0ustar www-datawww-data*.gem *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp mono_logger-1.1.1/Rakefile0000644000004100000410000000034014052722607015520 0ustar www-datawww-datarequire "bundler/gem_tasks" require 'rake/testtask' Rake::TestTask.new do |t| t.libs << "lib" t.test_files = FileList['test/*_test.rb'] t.ruby_opts = ['-w'] t.verbose = true end task :default => :test mono_logger-1.1.1/lib/0000755000004100000410000000000014052722607014624 5ustar www-datawww-datamono_logger-1.1.1/lib/mono_logger/0000755000004100000410000000000014052722607017133 5ustar www-datawww-datamono_logger-1.1.1/lib/mono_logger/version.rb0000644000004100000410000000011114052722607021136 0ustar www-datawww-datarequire 'logger' class MonoLogger < Logger VERSION = "1.1.1" end mono_logger-1.1.1/lib/mono_logger.rb0000644000004100000410000000363714052722607017471 0ustar www-datawww-datarequire 'mono_logger/version' require 'logger' #== MonoLogger # A subclass of Ruby's stdlib Logger with all the mutex and logrotation stuff # ripped out. class MonoLogger < Logger # # === Synopsis # # MonoLogger.new(STDOUT) # MonoLogger.new(filename) # # === Args # # +logdev+:: # The log device. This is a filename (String) or IO object (typically # +STDOUT+, +STDERR+, or an open file). # +shift_age+:: # ignored in MonoLogger # +shift_size+:: # ignored in MonoLogger # # === Description # # Create an instance. # def initialize(logdev, shift_age=nil, shift_size=nil) @progname = nil @level = DEBUG @default_formatter = Formatter.new @formatter = nil @logdev = nil if logdev @logdev = LocklessLogDevice.new(logdev) end end class LocklessLogDevice < LogDevice def initialize(log = nil) @dev = @filename = @shift_age = @shift_size = nil if log.respond_to?(:write) and log.respond_to?(:close) @dev = log else @dev = open_logfile(log) @dev.sync = true @filename = log end end def write(message) @dev.write(message) rescue Exception => ignored warn("log writing failed. #{ignored}") end def close @dev.close rescue nil end private def open_logfile(filename) if (FileTest.exist?(filename)) open(filename, (File::WRONLY | File::APPEND)) else create_logfile(filename) end end def create_logfile(filename) logdev = open(filename, (File::WRONLY | File::APPEND | File::CREAT)) logdev.sync = true add_log_header(logdev) logdev end def add_log_header(file) file.write( "# Logfile created on %s by %s\n" % [Time.now.to_s, Logger::ProgName] ) end end end mono_logger-1.1.1/Gemfile0000644000004100000410000000035014052722607015347 0ustar www-datawww-datasource 'https://rubygems.org' # Specify your gem's dependencies in mono_logger.gemspec gemspec if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.2.0") gem 'logger-application' end gem 'coveralls', require: false mono_logger-1.1.1/.github/0000755000004100000410000000000014052722607015416 5ustar www-datawww-datamono_logger-1.1.1/.github/workflows/0000755000004100000410000000000014052722607017453 5ustar www-datawww-datamono_logger-1.1.1/.github/workflows/build.yml0000644000004100000410000000135314052722607021277 0ustar www-datawww-dataname: Build on: [push, pull_request] jobs: build: name: >- ruby ${{ matrix.ruby }} # Available hosts. # https://docs.github.com/en/free-pro-team@latest/actions/reference/specifications-for-github-hosted-runners runs-on: ubuntu-20.04 # focal strategy: matrix: ruby: - '3.0' - 2.7 - 2.6 - 2.5 - 2.4 - 2.3 - 2.2 - 2.1 fail-fast: false steps: - uses: actions/checkout@v2 # https://github.com/ruby/setup-ruby - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - run: ruby -v - run: bundle install - run: bundle exec rake mono_logger-1.1.1/LICENSE.txt0000644000004100000410000000206614052722607015705 0ustar www-datawww-dataCopyright (c) 2013 Steve Klabnik 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.