diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6288bcd..fac7d18 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,6 +16,9 @@ jobs: matrix: ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} os: [ ubuntu-latest, macos-latest, windows-latest ] + exclude: + - ruby: 2.5 + os: macos-latest runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/LICENSE.txt b/BSDL similarity index 83% rename from LICENSE.txt rename to BSDL index a009cae..66d9359 100644 --- a/LICENSE.txt +++ b/BSDL @@ -4,10 +4,10 @@ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. + notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 8960ad3..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,25 +0,0 @@ -# HEAD - -* Support fiber-local log levels using `Logger#with_level` [#84](https://github.com/ruby/logger/issue/84) - -# 1.4.2 - -* Document that shift_age of 0 disables log file rotation [#43](https://github.com/ruby/logger/pull/43) (thanks to jeremyevans) -* Raise ArgumentError for invalid shift_age [#42](https://github.com/ruby/logger/pull/42) (thanks to jeremyevans) -* Honor Logger#level overrides [#41](https://github.com/ruby/logger/pull/41) (thanks to georgeclaghorn) - -# 1.4.1 - -Fixes: - -* Add missing files in gem (thanks to hsbt) - -# 1.4.0 - -Enhancements: - -* Add support for changing severity using bang methods [#15](https://github.com/ruby/logger/pull/15) (thanks to ioquatix) -* Set filename when initializing logger with a File object to make reopen work [#30](https://github.com/ruby/logger/pull/30) (thanks to jeremyevans) -* Add option to set the binary mode of the log device [#33](https://github.com/ruby/logger/pull/33) (thanks to refaelfranca) - -Also, large refactorings of codes and testing libraries are introduced. diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..48e5a96 --- /dev/null +++ b/COPYING @@ -0,0 +1,56 @@ +Ruby is copyrighted free software by Yukihiro Matsumoto . +You can redistribute it and/or modify it under either the terms of the +2-clause BSDL (see the file BSDL), or the conditions below: + +1. You may make and give away verbatim copies of the source form of the + software without restriction, provided that you duplicate all of the + original copyright notices and associated disclaimers. + +2. You may modify your copy of the software in any way, provided that + you do at least ONE of the following: + + a. place your modifications in the Public Domain or otherwise + make them Freely Available, such as by posting said + modifications to Usenet or an equivalent medium, or by allowing + the author to include your modifications in the software. + + b. use the modified software only within your corporation or + organization. + + c. give non-standard binaries non-standard names, with + instructions on where to get the original software distribution. + + d. make other distribution arrangements with the author. + +3. You may distribute the software in object code or binary form, + provided that you do at least ONE of the following: + + a. distribute the binaries and library files of the software, + together with instructions (in the manual page or equivalent) + on where to get the original distribution. + + b. accompany the distribution with the machine-readable source of + the software. + + c. give non-standard binaries non-standard names, with + instructions on where to get the original software distribution. + + d. make other distribution arrangements with the author. + +4. You may modify and include the part of the software into any other + software (possibly commercial). But some files in the distribution + are not written by the author, so that they are not under these terms. + + For the list of those files and their copying conditions, see the + file LEGAL. + +5. The scripts and library files supplied as input to or produced as + output from the software do not automatically fall under the + copyright of the software, but belong to whomever generated them, + and may be sold commercially, and may be aggregated with this + software. + +6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. diff --git a/lib/logger.rb b/lib/logger.rb index 4be5c33..4735209 100644 --- a/lib/logger.rb +++ b/lib/logger.rb @@ -381,7 +381,7 @@ class Logger # Logging severity threshold (e.g. Logger::INFO). def level - @level_override[Fiber.current] || @level + level_override[Fiber.current] || @level end # Sets the log level; returns +severity+. @@ -406,14 +406,14 @@ def level=(severity) # logger.debug { "Hello" } # end def with_level(severity) - prev, @level_override[Fiber.current] = level, Severity.coerce(severity) + prev, level_override[Fiber.current] = level, Severity.coerce(severity) begin yield ensure if prev - @level_override[Fiber.current] = prev + level_override[Fiber.current] = prev else - @level_override.delete(Fiber.current) + level_override.delete(Fiber.current) end end end @@ -574,10 +574,14 @@ def fatal!; self.level = FATAL; end # - +shift_period_suffix+: sets the format for the filename suffix # for periodic log file rotation; default is '%Y%m%d'. # See {Periodic Rotation}[rdoc-ref:Logger@Periodic+Rotation]. + # - +reraise_write_errors+: An array of exception classes, which will + # be reraised if there is an error when writing to the log device. + # The default is to swallow all exceptions raised. # def initialize(logdev, shift_age = 0, shift_size = 1048576, level: DEBUG, progname: nil, formatter: nil, datetime_format: nil, - binmode: false, shift_period_suffix: '%Y%m%d') + binmode: false, shift_period_suffix: '%Y%m%d', + reraise_write_errors: []) self.level = level self.progname = progname @default_formatter = Formatter.new @@ -589,7 +593,8 @@ def initialize(logdev, shift_age = 0, shift_size = 1048576, level: DEBUG, @logdev = LogDevice.new(logdev, shift_age: shift_age, shift_size: shift_size, shift_period_suffix: shift_period_suffix, - binmode: binmode) + binmode: binmode, + reraise_write_errors: reraise_write_errors) end end @@ -741,6 +746,11 @@ def format_severity(severity) SEV_LABEL[severity] || 'ANY' end + # Guarantee the existence of this ivar even when subclasses don't call the superclass constructor. + def level_override + @level_override ||= {} + end + def format_message(severity, datetime, progname, msg) (@formatter || @default_formatter).call(severity, datetime, progname, msg) end diff --git a/lib/logger/log_device.rb b/lib/logger/log_device.rb index 84277a2..4876adf 100644 --- a/lib/logger/log_device.rb +++ b/lib/logger/log_device.rb @@ -11,9 +11,10 @@ class LogDevice attr_reader :filename include MonitorMixin - def initialize(log = nil, shift_age: nil, shift_size: nil, shift_period_suffix: nil, binmode: false) + def initialize(log = nil, shift_age: nil, shift_size: nil, shift_period_suffix: nil, binmode: false, reraise_write_errors: []) @dev = @filename = @shift_age = @shift_size = @shift_period_suffix = nil @binmode = binmode + @reraise_write_errors = reraise_write_errors mon_initialize set_dev(log) if @filename @@ -34,16 +35,22 @@ def write(message) if @shift_age and @dev.respond_to?(:stat) begin check_shift_log + rescue *@reraise_write_errors + raise rescue warn("log shifting failed. #{$!}") end end begin @dev.write(message) + rescue *@reraise_write_errors + raise rescue warn("log writing failed. #{$!}") end end + rescue *@reraise_write_errors + raise rescue Exception => ignored warn("log writing failed. #{ignored}") end diff --git a/lib/logger/period.rb b/lib/logger/period.rb index 0a291db..a0359de 100644 --- a/lib/logger/period.rb +++ b/lib/logger/period.rb @@ -8,14 +8,14 @@ module Period def next_rotate_time(now, shift_age) case shift_age - when 'daily' + when 'daily', :daily t = Time.mktime(now.year, now.month, now.mday) + SiD - when 'weekly' + when 'weekly', :weekly t = Time.mktime(now.year, now.month, now.mday) + SiD * (7 - now.wday) - when 'monthly' + when 'monthly', :monthly t = Time.mktime(now.year, now.month, 1) + SiD * 32 return Time.mktime(t.year, t.month, 1) - when 'now', 'everytime' + when 'now', 'everytime', :now, :everytime return now else raise ArgumentError, "invalid :shift_age #{shift_age.inspect}, should be daily, weekly, monthly, or everytime" @@ -30,13 +30,13 @@ def next_rotate_time(now, shift_age) def previous_period_end(now, shift_age) case shift_age - when 'daily' + when 'daily', :daily t = Time.mktime(now.year, now.month, now.mday) - SiD / 2 - when 'weekly' + when 'weekly', :weekly t = Time.mktime(now.year, now.month, now.mday) - (SiD * now.wday + SiD / 2) - when 'monthly' + when 'monthly', :monthly t = Time.mktime(now.year, now.month, 1) - SiD / 2 - when 'now', 'everytime' + when 'now', 'everytime', :now, :everytime return now else raise ArgumentError, "invalid :shift_age #{shift_age.inspect}, should be daily, weekly, monthly, or everytime" diff --git a/lib/logger/version.rb b/lib/logger/version.rb index 202b6e4..2a0801b 100644 --- a/lib/logger/version.rb +++ b/lib/logger/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true class Logger - VERSION = "1.6.0" + VERSION = "1.6.1" end diff --git a/test/logger/test_logger.rb b/test/logger/test_logger.rb index 37d0f58..2023553 100644 --- a/test/logger/test_logger.rb +++ b/test/logger/test_logger.rb @@ -113,6 +113,15 @@ def test_string_level assert_raise(ArgumentError) { @logger.level = 'something_wrong' } end + def test_reraise_write_errors + c = Object.new + e = Class.new(StandardError) + c.define_singleton_method(:write){|*| raise e} + c.define_singleton_method(:close){} + logger = Logger.new(c, :reraise_write_errors=>[e]) + assert_raise(e) { logger.warn('foo') } + end + def test_progname assert_nil(@logger.progname) @logger.progname = "name" diff --git a/test/logger/test_logperiod.rb b/test/logger/test_logperiod.rb index 6e6e5e9..ee38d87 100644 --- a/test/logger/test_logperiod.rb +++ b/test/logger/test_logperiod.rb @@ -1,80 +1,67 @@ # coding: US-ASCII # frozen_string_literal: false -require 'logger' -require 'time' +require "logger" +require "time" class TestLogPeriod < Test::Unit::TestCase def test_next_rotate_time time = Time.parse("2019-07-18 13:52:02") - daily_result = Logger::Period.next_rotate_time(time, 'daily') - next_day = Time.parse("2019-07-19 00:00:00") - assert_equal(next_day, daily_result) + assert_next_rotate_time_words(time, "2019-07-19 00:00:00", ["daily", :daily]) + assert_next_rotate_time_words(time, "2019-07-21 00:00:00", ["weekly", :weekly]) + assert_next_rotate_time_words(time, "2019-08-01 00:00:00", ["monthly", :monthly]) - weekly_result = Logger::Period.next_rotate_time(time, 'weekly') - next_week = Time.parse("2019-07-21 00:00:00") - assert_equal(next_week, weekly_result) - - monthly_result = Logger::Period.next_rotate_time(time, 'monthly') - next_month = Time.parse("2019-08-1 00:00:00") - assert_equal(next_month, monthly_result) - - assert_raise(ArgumentError) { Logger::Period.next_rotate_time(time, 'invalid') } + assert_raise(ArgumentError) { Logger::Period.next_rotate_time(time, "invalid") } end def test_next_rotate_time_extreme_cases # First day of Month and Saturday time = Time.parse("2018-07-01 00:00:00") - daily_result = Logger::Period.next_rotate_time(time, 'daily') - next_day = Time.parse("2018-07-02 00:00:00") - assert_equal(next_day, daily_result) - - weekly_result = Logger::Period.next_rotate_time(time, 'weekly') - next_week = Time.parse("2018-07-08 00:00:00") - assert_equal(next_week, weekly_result) + assert_next_rotate_time_words(time, "2018-07-02 00:00:00", ["daily", :daily]) + assert_next_rotate_time_words(time, "2018-07-08 00:00:00", ["weekly", :weekly]) + assert_next_rotate_time_words(time, "2018-08-01 00:00:00", ["monthly", :monthly]) - monthly_result = Logger::Period.next_rotate_time(time, 'monthly') - next_month = Time.parse("2018-08-1 00:00:00") - assert_equal(next_month, monthly_result) - - assert_raise(ArgumentError) { Logger::Period.next_rotate_time(time, 'invalid') } + assert_raise(ArgumentError) { Logger::Period.next_rotate_time(time, "invalid") } end def test_previous_period_end time = Time.parse("2019-07-18 13:52:02") - daily_result = Logger::Period.previous_period_end(time, 'daily') - day_ago = Time.parse("2019-07-17 23:59:59") - assert_equal(day_ago, daily_result) - - weekly_result = Logger::Period.previous_period_end(time, 'weekly') - week_ago = Time.parse("2019-07-13 23:59:59") - assert_equal(week_ago, weekly_result) - - monthly_result = Logger::Period.previous_period_end(time, 'monthly') - month_ago = Time.parse("2019-06-30 23:59:59") - assert_equal(month_ago, monthly_result) + assert_previous_period_end_words(time, "2019-07-17 23:59:59", ["daily", :daily]) + assert_previous_period_end_words(time, "2019-07-13 23:59:59", ["weekly", :weekly]) + assert_previous_period_end_words(time, "2019-06-30 23:59:59", ["monthly", :monthly]) - assert_raise(ArgumentError) { Logger::Period.next_rotate_time(time, 'invalid') } + assert_raise(ArgumentError) { Logger::Period.previous_period_end(time, "invalid") } end def test_previous_period_end_extreme_cases # First day of Month and Saturday time = Time.parse("2018-07-01 00:00:00") + previous_date = "2018-06-30 23:59:59" - daily_result = Logger::Period.previous_period_end(time, 'daily') - day_ago = Time.parse("2018-06-30 23:59:59") - assert_equal(day_ago, daily_result) + assert_previous_period_end_words(time, previous_date, ["daily", :daily]) + assert_previous_period_end_words(time, previous_date, ["weekly", :weekly]) + assert_previous_period_end_words(time, previous_date, ["monthly", :monthly]) - weekly_result = Logger::Period.previous_period_end(time, 'weekly') - week_ago = Time.parse("2018-06-30 23:59:59") - assert_equal(week_ago, weekly_result) + assert_raise(ArgumentError) { Logger::Period.previous_period_end(time, "invalid") } + end + + private - monthly_result = Logger::Period.previous_period_end(time, 'monthly') - month_ago = Time.parse("2018-06-30 23:59:59") - assert_equal(month_ago, monthly_result) + def assert_next_rotate_time_words(time, next_date, words) + assert_time_words(:next_rotate_time, time, next_date, words) + end + + def assert_previous_period_end_words(time, previous_date, words) + assert_time_words(:previous_period_end, time, previous_date, words) + end - assert_raise(ArgumentError) { Logger::Period.next_rotate_time(time, 'invalid') } + def assert_time_words(method, time, date, words) + words.each do |word| + daily_result = Logger::Period.public_send(method, time, word) + expected_result = Time.parse(date) + assert_equal(expected_result, daily_result) + end end end