diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 7d06100..84ad6ea 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -1,4 +1,5 @@ name: Continuous Integration + on: push: branches: @@ -7,64 +8,71 @@ on: tags: - v* pull_request: + branches-ignore: + - actions-* + env: BUNDLE_CLEAN: "true" BUNDLE_PATH: vendor/bundle BUNDLE_JOBS: 3 BUNDLE_RETRY: 3 + jobs: - specs: - name: ruby-${{ matrix.ruby }} ${{ matrix.appraisal }} + build: runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - ruby: "ruby" - stanardrb: true + standardrb: true - ruby: "jruby" - - ruby: "3" + - ruby: "3.2" + - ruby: "3.1" - ruby: "3.0" - ruby: "2.7" - ruby: "2.6" - ruby: "2.5" - ruby: "2.4" - ruby: "2.3" + - ruby: "ruby" + appraisal: activesupport_latest + - ruby: "3.1" + appraisal: activesupport_7 + - ruby: "3.0" + appraisal: activesupport_6 + - ruby: "2.6" + appraisal: activesupport_5 - ruby: "2.5" appraisal: activesupport_4 bundler: "1.17.3" - - ruby: "2.5" - appraisal: activesupport_5 - - ruby: "2.6" - appraisal: activesupport - - ruby: "2.6" - appraisal: logger + - ruby: "ruby" + appraisal: logger_gem steps: - - name: Checkout code - uses: actions/checkout@v2 - - name: Setup Ruby + - uses: actions/checkout@v2 + - name: Set up Ruby ${{ matrix.ruby }} uses: ruby/setup-ruby@v1 with: - ruby-version: ${{ matrix.ruby }} + ruby-version: "${{ matrix.ruby }}" + - name: Install packages + run: | + sudo apt-get update + sudo apt-get install libsqlite3-dev - name: Setup bundler if: matrix.bundler != '' run: | gem uninstall bundler --all gem install bundler --no-document --version ${{ matrix.bundler }} - - name: Remove standardrb - if: matrix.ruby == '2.3' - run: sed -i 's/gem "standard"/# gem "standard"/' Gemfile - name: Set Appraisal bundle if: matrix.appraisal != '' run: | echo "using gemfile gemfiles/${{ matrix.appraisal }}.gemfile" bundle config set gemfile "gemfiles/${{ matrix.appraisal }}.gemfile" - - name: Install bundle + - name: Install gems run: | bundle update - - name: Run specs - if: matrix.job == 'rspec' - run: bundle exec rake spec - - name: Run standardrb + - name: Run Tests + run: bundle exec rake + - name: standardrb if: matrix.standardrb == true - run: bundle exec standardrb + run: bundle exec rake standard diff --git a/.gitignore b/.gitignore index 5784fac..d11af32 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,14 @@ -pkg -tmp -rdoc -*.rbc +.DS_Store +.bundle/ +.rspec +.rspec_status .ruby-version -.tm_properties +.yardoc/ +.env Gemfile.lock +coverage/ gemfiles/*.gemfile.lock +log/*.log +pkg/ +rdoc/ +doc/ \ No newline at end of file diff --git a/Appraisals b/Appraisals index 7dd3d22..d36c7ed 100644 --- a/Appraisals +++ b/Appraisals @@ -1,11 +1,19 @@ -appraise "logger" do +appraise "logger-gem" do gem "logger" end -appraise "activesupport" do +appraise "activesupport-latest" do gem "activesupport", require: "activesupport/all" end +appraise "activesupport-7" do + gem "activesupport", "~> 7.0", require: "activesupport/all" +end + +appraise "activesupport-6" do + gem "activesupport", "~> 6.0", require: "activesupport/all" +end + appraise "activesupport-5" do gem "activesupport", "~> 5.0", require: "activesupport/all" end diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a681bc..995c503 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,129 +1,182 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 1.2.9 + +### Added +- Allow passing in formatters as class names when adding them. +- Allow passing in formatters initialization arguments when adding them. +- Add truncate formatter for capping the length of log messages. + ## 1.2.8 -* Add `Logger#untagged` to remove previously set logging tags from a block. -* Return result of the block when a block is passed to `Logger#tag`. +### Added +- Add `Logger#untagged` to remove previously set logging tags from a block. +- Return result of the block when a block is passed to `Logger#tag`. ## 1.2.7 -* Allow passing frozen hashes to `Logger#tag`. Tags passed to this method are now duplicated so the logger maintains it's own copy of the hash. +### Fixed +- Allow passing frozen hashes to `Logger#tag`. Tags passed to this method are now duplicated so the logger maintains it's own copy of the hash. ## 1.2.6 -* Fix `Logger#tag` so it only ads to the current block's logger tags instead of the global tags if called inside a `Logger#tag` block. -* Add Logger#remove_tag +### Added +- Add Logger#remove_tag + +### Fixed +- Fix `Logger#tag` so it only ads to the current block's logger tags instead of the global tags if called inside a `Logger#tag` block. + ## 1.2.5 -* Fix logic with recursive reference guard in StructuredFormatter so it only suppresses Enumerable references. -* Add support for bang methods (error!) for setting the log level. +### Added +- Add support for bang methods (error!) for setting the log level. + +### Fixed +- Fixed logic with recursive reference guard in StructuredFormatter so it only suppresses Enumerable references. ## 1.2.4 -* Enhance `ActiveSupport::TaggedLogging` support so code that Lumberjack loggers can be wrapped with a tagged logger. +### Added +- Enhance `ActiveSupport::TaggedLogging` support so code that Lumberjack loggers can be wrapped with a tagged logger. ## 1.2.3 -* Fix structured formatter so no-recursive, duplicate references are allowed. +### Fixed +- Fix structured formatter so no-recursive, duplicate references are allowed. ## 1.2.2 -* Prevent infinite loops in the structured formatter where objects have backreferences to each other. +### Fixed +- Prevent infinite loops in the structured formatter where objects have backreferences to each other. ## 1.2.1 -* Prevent infinite loops where logging a statement triggers the logger. +### Fixed +- Prevent infinite loops where logging a statement triggers the logger. ## 1.2.0 -* Enable compatibility with `ActiveSupport::TaggedLogger` by calling `tagged_logger!` on a logger. -* Add `tag_formatter` to logger to specify formatting of tags for output. -* Allow adding and removing classes by name to formatters. -* Allow adding and removing multiple classes in a single call to a formatter. -* Allow using symbols and strings as log level for silencing a logger. -* Ensure flusher thread gets stopped when logger is closed. -* Add writer for logger device attribute. -* Handle passing an array of devices to a multi device. -* Helper method to get a tag with a specified name. -* Add strip formatter to strip whitespace from strings. -* Support non-alpha numeric characters in template variables. -* Add backtrace cleaner to ExceptionFormatter. +### Added +- Enable compatibility with `ActiveSupport::TaggedLogger` by calling `tagged_logger!` on a logger. +- Add `tag_formatter` to logger to specify formatting of tags for output. +- Allow adding and removing classes by name to formatters. +- Allow adding and removing multiple classes in a single call to a formatter. +- Allow using symbols and strings as log level for silencing a logger. +- Ensure flusher thread gets stopped when logger is closed. +- Add writer for logger device attribute. +- Handle passing an array of devices to a multi device. +- Helper method to get a tag with a specified name. +- Add strip formatter to strip whitespace from strings. +- Support non-alpha numeric characters in template variables. +- Add backtrace cleaner to ExceptionFormatter. ## 1.1.1 -* Replace Procs in tag values with the value of calling the Proc in log entries. +### Added +- Replace Procs in tag values with the value of calling the Proc in log entries. ## 1.1.0 -* Change `Lumberjack::Logger` to inherit from ::Logger -* Add support for tags on log messages -* Add global tag context for all loggers -* Add per logger tags and tag contexts -* Reimplement unit of work id as a tag on log entries -* Add support for setting datetime format on log devices -* Performance optimizations -* Add Multi device to output to multiple devices -* Add `DateTimeFormatter`, `IdFormatter`, `ObjectFormatter`, and `StructuredFormatter` -* Add rack `Context` middleware for setting thread global context -* End support for ruby versions < 2.3 -* Add support for modules in formatters +### Added +- Change `Lumberjack::Logger` to inherit from ::Logger +- Add support for tags on log messages +- Add global tag context for all loggers +- Add per logger tags and tag contexts +- Reimplement unit of work id as a tag on log entries +- Add support for setting datetime format on log devices +- Performance optimizations +- Add Multi device to output to multiple devices +- Add `DateTimeFormatter`, `IdFormatter`, `ObjectFormatter`, and `StructuredFormatter` +- Add rack `Context` middleware for setting thread global context +- Add support for modules in formatters + +### Removed +- End support for ruby versions < 2.3 ## 1.0.13 -* Reduce amount of code executed inside a mutex lock when writing to the logger stream. -* Added `:min_roll_check` option to `Lumberjack::Device::RollingLogFile` to reduce file system checks. Default is now to only check if a file needs to be rolled at most once per second. -* Force immutable strings for Ruby versions that support them. +### Added +- Added `:min_roll_check` option to `Lumberjack::Device::RollingLogFile` to reduce file system checks. Default is now to only check if a file needs to be rolled at most once per second. +- Force immutable strings for Ruby versions that support them. + +### Changed +- Reduce amount of code executed inside a mutex lock when writing to the logger stream. ## 1.0.12 -* Add support for `ActionDispatch` request id for better Rails compatibility. +### Added +- Add support for `ActionDispatch` request id for better Rails compatibility. ## 1.0.11 -* Fix Ruby 2.4 deprecation warning on Fixnum (thanks koic). -* Fix gemspec files to be flat array (thanks e2). +### Fixed +- Fix Ruby 2.4 deprecation warning on Fixnum (thanks koic). +- Fix gemspec files to be flat array (thanks e2). ## 1.0.10 -* Expose option to manually roll log files. -* Minor code cleanup. +### Added +- Expose option to manually roll log files. + +### Changed +- Minor code cleanup. ## 1.0.9 -* Add method so Formatter is compatible with `ActiveSupport` logging extensions. +### Added +- Add method so Formatter is compatible with `ActiveSupport` logging extensions. ## 1.0.8 -* Fix another internal variable name conflict with `ActiveSupport` logging extensions. +### Fixed +- Fix another internal variable name conflict with `ActiveSupport` logging extensions. ## 1.0.7 -* Fix broken formatter attribute method. +### Fixed +- Fix broken formatter attribute method. ## 1.0.6 -* Fix internal variable name conflict with `ActiveSupport` logging extensions. +### Fixed +- Fix internal variable name conflict with `ActiveSupport` logging extensions. ## 1.0.5 -* Update docs. -* Remove autoload calls to make thread safe. -* Make compatible with Ruby 2.1.1 Pathname. -* Make compatible with standard library Logger's use of progname as default message. +### Changed +- Update docs. +- Remove autoload calls to make thread safe. +- Make compatible with Ruby 2.1.1 Pathname. +- Make compatible with standard library Logger's use of progname as default message. ## 1.0.4 -* Add ability to supply a unit of work id for a block instead of having one generated every time. +### Added +- Add ability to supply a unit of work id for a block instead of having one generated every time. ## 1.0.3 -* Change log file output format to binary to avoid encoding warnings. -* Fixed bug in log file rolling that left the file locked. +### Fixed +- Change log file output format to binary to avoid encoding warnings. +- Fixed bug in log file rolling that left the file locked. ## 1.0.2 -* Remove deprecation warnings under ruby 1.9.3. -* Add more error checking around file rolling. +### Fixed +- Remove deprecation warnings under ruby 1.9.3. +- Add more error checking around file rolling. ## 1.0.1 -* Writes are no longer buffered by default. +### Fixed +- Writes are no longer buffered by default. + +## 1.0.0 + +### Added +- Initial release diff --git a/Gemfile b/Gemfile index ac92db3..9f8c6bd 100644 --- a/Gemfile +++ b/Gemfile @@ -1,19 +1,10 @@ source "https://rubygems.org" -group :runtime do - gemspec -end - -group :development, :test do - gem "rake" - gem "rspec", "~> 3.9" - gem "timecop" - gem "appraisal" - - # Lock standard to a particular version, esp. cause it's still 0.x.x according to Semver - gem "standard", "0.5.2", require: false -end - -group :doc do - gem "yard" -end +gemspec + +gem "rake" +gem "rspec", "~> 3.12" +gem "timecop" +gem "appraisal" +gem "standard", "1.0", require: false +gem "yard" diff --git a/Rakefile b/Rakefile index bf3f641..7950adb 100644 --- a/Rakefile +++ b/Rakefile @@ -1,9 +1,19 @@ +begin + require "bundler/setup" +rescue LoadError + puts "You must `gem install bundler` and `bundle install` to run rake tasks" +end + +require "yard" +YARD::Rake::YardocTask.new(:yard) + require "bundler/gem_tasks" + require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) -task default: :appraisals +task default: :spec desc "run the specs using appraisal" task :appraisals do @@ -16,3 +26,5 @@ namespace :appraisals do exec "bundle exec appraisal install" end end + +require "standard/rake" diff --git a/VERSION b/VERSION index db6fb4a..9d4f823 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.8 +1.2.9 diff --git a/gemfiles/activesupport_4.gemfile b/gemfiles/activesupport_4.gemfile index 717ec19..fe80e55 100644 --- a/gemfiles/activesupport_4.gemfile +++ b/gemfiles/activesupport_4.gemfile @@ -2,23 +2,12 @@ source "https://rubygems.org" +gem "rake" +gem "rspec", "~> 3.12" +gem "timecop" +gem "appraisal" +gem "standard", "1.0", require: false +gem "yard" gem "activesupport", "~> 4.0", require: "activesupport/all" -group :runtime do - gemspec path: "../" -end - -group :development, :test do - gem "rake" - gem "rspec", "~> 3.9" - gem "timecop" - gem "appraisal" -end - -group :standardrb do - gem "standard", "0.5.2", require: false -end - -group :doc do - gem "yard" -end +gemspec path: "../" diff --git a/gemfiles/activesupport_5.gemfile b/gemfiles/activesupport_5.gemfile index 377c60e..732904e 100644 --- a/gemfiles/activesupport_5.gemfile +++ b/gemfiles/activesupport_5.gemfile @@ -2,23 +2,12 @@ source "https://rubygems.org" +gem "rake" +gem "rspec", "~> 3.12" +gem "timecop" +gem "appraisal" +gem "standard", "1.0", require: false +gem "yard" gem "activesupport", "~> 5.0", require: "activesupport/all" -group :runtime do - gemspec path: "../" -end - -group :development, :test do - gem "rake" - gem "rspec", "~> 3.9" - gem "timecop" - gem "appraisal" -end - -group :standardrb do - gem "standard", "0.5.2", require: false -end - -group :doc do - gem "yard" -end +gemspec path: "../" diff --git a/gemfiles/activesupport_6.gemfile b/gemfiles/activesupport_6.gemfile new file mode 100644 index 0000000..264e69a --- /dev/null +++ b/gemfiles/activesupport_6.gemfile @@ -0,0 +1,13 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rake" +gem "rspec", "~> 3.12" +gem "timecop" +gem "appraisal" +gem "standard", "1.0", require: false +gem "yard" +gem "activesupport", "~> 6.0", require: "activesupport/all" + +gemspec path: "../" diff --git a/gemfiles/activesupport_7.gemfile b/gemfiles/activesupport_7.gemfile new file mode 100644 index 0000000..5bc6fe2 --- /dev/null +++ b/gemfiles/activesupport_7.gemfile @@ -0,0 +1,13 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rake" +gem "rspec", "~> 3.12" +gem "timecop" +gem "appraisal" +gem "standard", "1.0", require: false +gem "yard" +gem "activesupport", "~> 7.0", require: "activesupport/all" + +gemspec path: "../" diff --git a/gemfiles/activesupport_latest.gemfile b/gemfiles/activesupport_latest.gemfile new file mode 100644 index 0000000..f3674c9 --- /dev/null +++ b/gemfiles/activesupport_latest.gemfile @@ -0,0 +1,13 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rake" +gem "rspec", "~> 3.12" +gem "timecop" +gem "appraisal" +gem "standard", "1.0", require: false +gem "yard" +gem "activesupport", require: "activesupport/all" + +gemspec path: "../" diff --git a/gemfiles/logger_gem.gemfile b/gemfiles/logger_gem.gemfile new file mode 100644 index 0000000..9dbde21 --- /dev/null +++ b/gemfiles/logger_gem.gemfile @@ -0,0 +1,13 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rake" +gem "rspec", "~> 3.12" +gem "timecop" +gem "appraisal" +gem "standard", "1.0", require: false +gem "yard" +gem "logger" + +gemspec path: "../" diff --git a/lib/lumberjack.rb b/lib/lumberjack.rb index 093c4a7..1529f31 100644 --- a/lib/lumberjack.rb +++ b/lib/lumberjack.rb @@ -6,7 +6,7 @@ require "logger" module Lumberjack - LINE_SEPARATOR = (RbConfig::CONFIG["host_os"] =~ /mswin/i ? "\r\n" : "\n") + LINE_SEPARATOR = ((RbConfig::CONFIG["host_os"] =~ /mswin/i) ? "\r\n" : "\n") require_relative "lumberjack/severity" require_relative "lumberjack/formatter" @@ -32,6 +32,9 @@ class << self # # For the common use case of treating a single web request as a unit of work, see the # Lumberjack::Rack::UnitOfWork class. + # + # @param [String] id The id for the unit of work. + # @return [void] def unit_of_work(id = nil) id ||= SecureRandom.hex(6) context do @@ -41,6 +44,8 @@ def unit_of_work(id = nil) end # Get the UniqueIdentifier for the current unit of work. + # + # @return [String, nil] The id for the current unit of work. def unit_of_work_id context[:unit_of_work_id] end @@ -53,6 +58,8 @@ def unit_of_work_id # # Otherwise, it will return the current context. If one doesn't exist, it will return a new one # but that context will not be in any scope. + # + # @return [Lumberjack::Context] The current context if called without a block. def context(&block) current_context = Thread.current[:lumberjack_context] if block @@ -63,6 +70,9 @@ def context(&block) end # Set the context to use within a block. + # + # @param [Lumberjack::Context] context The context to use within the block. + # @return [Object] The result of the block. def use_context(context, &block) current_context = Thread.current[:lumberjack_context] begin @@ -74,17 +84,24 @@ def use_context(context, &block) end # Return true if inside a context block. + # + # @return [Boolean] def context? !!Thread.current[:lumberjack_context] end # Return the tags from the current context or nil if there are no tags. + # + # @return [Hash, nil] def context_tags context = Thread.current[:lumberjack_context] context&.tags end # Set tags on the current context + # + # @param [Hash] tags The tags to set. + # @return [void] def tag(tags) context = Thread.current[:lumberjack_context] context&.tag(tags) diff --git a/lib/lumberjack/context.rb b/lib/lumberjack/context.rb index f5d6656..bb4982d 100644 --- a/lib/lumberjack/context.rb +++ b/lib/lumberjack/context.rb @@ -5,12 +5,16 @@ module Lumberjack class Context attr_reader :tags + # @param [Context] parent_context A parent context to inherit tags from. def initialize(parent_context = nil) @tags = {} @tags.merge!(parent_context.tags) if parent_context end # Set tags on the context. + # + # @param [Hash] tags The tags to set. + # @return [void] def tag(tags) tags.each do |key, value| @tags[key.to_s] = value @@ -18,16 +22,25 @@ def tag(tags) end # Get a context tag. + # + # @param [String, Symbol] key The tag key. + # @return [Object] The tag value. def [](key) @tags[key.to_s] end # Set a context tag. + # + # @param [String, Symbol] key The tag key. + # @param [Object] value The tag value. + # @return [void] def []=(key, value) @tags[key.to_s] = value end # Clear all the context data. + # + # @return [void] def reset @tags.clear end diff --git a/lib/lumberjack/device.rb b/lib/lumberjack/device.rb index 1ee219c..215c756 100644 --- a/lib/lumberjack/device.rb +++ b/lib/lumberjack/device.rb @@ -13,29 +13,44 @@ class Device require_relative "device/null" # Subclasses must implement this method to write a LogEntry. + # + # @param [Lumberjack::LogEntry] entry The entry to write. + # @return [void] def write(entry) raise NotImplementedError end # Subclasses may implement this method to close the device. + # + # @return [void] def close flush end # Subclasses may implement this method to reopen the device. + # + # @param [Object] logdev The log device to use. + # @return [void] def reopen(logdev = nil) flush end # Subclasses may implement this method to flush any buffers used by the device. + # + # @return [void] def flush end # Subclasses may implement this method to get the format for log timestamps. + # + # @return [String] The format for log timestamps. def datetime_format end # Subclasses may implement this method to set a format for log timestamps. + # + # @param [String] format The format for log timestamps. + # @return [void] def datetime_format=(format) end end diff --git a/lib/lumberjack/device/date_rolling_log_file.rb b/lib/lumberjack/device/date_rolling_log_file.rb index b255303..ba66815 100644 --- a/lib/lumberjack/device/date_rolling_log_file.rb +++ b/lib/lumberjack/device/date_rolling_log_file.rb @@ -13,6 +13,9 @@ 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. + # + # @param [String, Pathname] path The path to the log file. + # @param [Hash] options The options for the device. def initialize(path, options = {}) @manual = options[:manual] @file_date = Date.today @@ -25,6 +28,9 @@ def initialize(path, options = {}) super end + # The date based suffix for file. + # + # @return [String] def archive_file_suffix case @roll_period when :weekly @@ -36,6 +42,9 @@ def archive_file_suffix end end + # Check if the file should be rolled. + # + # @return [Boolean] def roll_file? if @manual true diff --git a/lib/lumberjack/device/log_file.rb b/lib/lumberjack/device/log_file.rb index 7fd31b7..de18b5b 100644 --- a/lib/lumberjack/device/log_file.rb +++ b/lib/lumberjack/device/log_file.rb @@ -12,12 +12,19 @@ class LogFile < Writer attr_reader :path # Create a logger to the file at +path+. Options are passed through to the Writer constructor. + # + # @param [String, Pathname] path The path to the log file. + # @param [Hash] options The options for the device. def initialize(path, options = {}) @path = File.expand_path(path) FileUtils.mkdir_p(File.dirname(@path)) super(file_stream, options) end + # Reopen the log file. + # + # @param [Object] logdev not used + # @return [void] def reopen(logdev = nil) close @stream = file_stream diff --git a/lib/lumberjack/device/multi.rb b/lib/lumberjack/device/multi.rb index d8b77c5..d0924ff 100644 --- a/lib/lumberjack/device/multi.rb +++ b/lib/lumberjack/device/multi.rb @@ -4,6 +4,7 @@ module Lumberjack class Device # This is a logging device that forward log entries to multiple other devices. class Multi < Device + # @param [Array] devices The devices to write to. def initialize(*devices) @devices = devices.flatten end diff --git a/lib/lumberjack/device/rolling_log_file.rb b/lib/lumberjack/device/rolling_log_file.rb index 0c208de..cc80d09 100644 --- a/lib/lumberjack/device/rolling_log_file.rb +++ b/lib/lumberjack/device/rolling_log_file.rb @@ -20,10 +20,10 @@ def initialize(path, options = {}) @keep = options[:keep] super(path, options) @file_inode = begin - stream.lstat.ino - rescue - nil - end + stream.lstat.ino + rescue + nil + end @@rolls = [] @next_stat_check = Time.now.to_f @min_roll_check = (options[:min_roll_check] || 1.0).to_f @@ -42,15 +42,15 @@ def roll_file? # 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: + def roll_file! # :nodoc: do_once(stream) do archive_file = "#{path}.#{archive_file_suffix}" stream.flush current_inode = begin - File.stat(path).ino - rescue - nil - end + File.stat(path).ino + rescue + nil + end if @file_inode && current_inode == @file_inode && !File.exist?(archive_file) && File.exist?(path) begin File.rename(path, archive_file) @@ -78,10 +78,10 @@ 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 = begin - File.lstat(path).ino - rescue - nil - end + File.lstat(path).ino + rescue + nil + end if path_inode != @file_inode @file_inode = path_inode reopen_file @@ -98,10 +98,10 @@ def reopen_file new_stream = File.open(path, "a", encoding: EXTERNAL_ENCODING) new_stream.sync = true if buffer_size > 0 @file_inode = begin - new_stream.lstat.ino - rescue - nil - end + new_stream.lstat.ino + rescue + nil + end self.stream = new_stream old_stream.close end @@ -127,10 +127,10 @@ def do_once(file) end begin verify = begin - file.lstat - rescue - nil - end + file.lstat + rescue + nil + end # 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 diff --git a/lib/lumberjack/device/writer.rb b/lib/lumberjack/device/writer.rb index 24f129c..8018ffb 100644 --- a/lib/lumberjack/device/writer.rb +++ b/lib/lumberjack/device/writer.rb @@ -60,6 +60,9 @@ def clear # 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). + # + # @param [IO] stream The stream to write log entries to. + # @param [Hash] options The options for the device. def initialize(stream, options = {}) @lock = Mutex.new @stream = stream @@ -77,12 +80,18 @@ def initialize(stream, options = {}) # Set the buffer size in bytes. The device will only be physically written to when the buffer size # is exceeded. + # + # @param [Integer] value The size of the buffer in bytes. + # @return [void] 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. + # + # @param [LogEntry, String] entry The entry to write to the stream. + # @return [void] def write(entry) string = (entry.is_a?(LogEntry) ? @template.call(entry) : entry) return if string.nil? @@ -105,12 +114,16 @@ def write(entry) end # Close the underlying stream. + # + # @return [void] def close flush stream.close end # Flush the underlying stream. + # + # @return [void] def flush lines = nil @lock.synchronize do @@ -120,10 +133,17 @@ def flush write_to_stream(lines) if lines end + # Get the datetime format. + # + # @return [String] The datetime format. def datetime_format @template.datetime_format if @template.respond_to?(:datetime_format) end + # Set the datetime format. + # + # @param [String] format The datetime format. + # @return [void] def datetime_format=(format) if @template.respond_to?(:datetime_format=) @template.datetime_format = format diff --git a/lib/lumberjack/formatter.rb b/lib/lumberjack/formatter.rb index ea8e212..c2ac869 100644 --- a/lib/lumberjack/formatter.rb +++ b/lib/lumberjack/formatter.rb @@ -21,11 +21,14 @@ class Formatter require_relative "formatter/string_formatter" require_relative "formatter/strip_formatter" require_relative "formatter/structured_formatter" + require_relative "formatter/truncate_formatter" class << self # Returns a new empty formatter with no mapping. For historical reasons, a formatter # is initialized with mappings to help output objects as strings. This will return one # without the default mappings. + # + # @return [Lumberjack::Formatter] a new empty formatter def empty new.clear end @@ -45,7 +48,17 @@ def initialize # 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. + # The predefined formatters are: + # - :date_time + # - :exception + # - :id + # - :inspect + # - :object + # - :pretty_print + # - :string + # - :strip + # - :structured + # - :truncate # # You can add multiple classes at once by passing an array of classes. # @@ -53,7 +66,18 @@ def initialize # help avoid loading dependency issues. This applies only to classes; modules cannot be # passed in as strings. # - # === Examples + # @param [Class, Module, String, Array] klass The class or module to add a formatter for. + # @param [Symbol, Class, String, #call] formatter The formatter to use for the class. + # If a symbol is passed in, it will be used to load one of the predefined formatters. + # If a class is passed in, it will be initialized with the args passed in. + # Otherwise, the object will be used as the formatter and must respond to call method. + # @param [Array] args Arguments to pass to the formatter when it is initialized. + # @yield [obj] A block that will be used as the formatter for the class. + # @yieldparam [Object] obj The object to format. + # @yieldreturn [String] The formatted string. + # @return [self] Returns itself so that add statements can be chained together. + # + # @example # # # Use a predefined formatter # formatter.add(MyClass, :pretty_print) @@ -66,18 +90,27 @@ def initialize # # # Add statements can be chained together # formatter.add(MyClass, :pretty_print).add(YourClass){|obj| obj.humanize} - def add(klass, formatter = nil, &block) + def add(klass, formatter = nil, *args, &block) formatter ||= block if formatter.nil? remove(klass) else + formatter_class_name = nil 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 + elsif formatter.is_a?(String) + formatter_class_name = formatter + end + if formatter_class_name + formatter = Formatter.const_get(formatter_class_name) + end + + if formatter.is_a?(Class) + formatter = formatter.new(*args) end Array(klass).each do |k| - if k.class == Module + if k.instance_of?(Module) @module_formatters[k] = formatter else k = k.name if k.is_a?(Class) @@ -95,9 +128,12 @@ def add(klass, formatter = nil, &block) # You can also pass class names as strings instead of the classes themselves. This can # help avoid loading dependency issues. This applies only to classes; modules cannot be # passed in as strings. + # + # @param [Class, Module, String, Array] klass The class or module to remove the formatters for. + # @return [self] Returns itself so that remove statements can be chained together. def remove(klass) Array(klass).each do |k| - if k.class == Module + if k.instance_of?(Module) @module_formatters.delete(k) else k = k.name if k.is_a?(Class) @@ -108,13 +144,18 @@ def remove(klass) end # Remove all formatters including the default formatter. Can be chained to add method calls. + # + # @return [self] Returns itself so that clear statements can be chained together. def clear @class_formatters.clear @module_formatters.clear self end - # Format a message object as a string. + # Format a message object by applying all formatters attached to it. + # + # @param [Object] message The message object to format. + # @return [Object] The formatted object. def format(message) formatter = formatter_for(message.class) if formatter&.respond_to?(:call) @@ -126,6 +167,11 @@ def format(message) # Compatibility with the Logger::Formatter signature. This method will just convert the message # object to a string and ignores the other parameters. + # + # @param [Integer, String, Symbol] severity The severity of the message. + # @param [Time] timestamp The time the message was logged. + # @param [String] progname The name of the program logging the message. + # @param [Object] msg The message object to format. def call(severity, timestamp, progname, msg) "#{format(msg)}#{Lumberjack::LINE_SEPARATOR}" end @@ -133,7 +179,7 @@ def call(severity, timestamp, progname, msg) private # Find the formatter for a class by looking it up using the class hierarchy. - def formatter_for(klass) #:nodoc: + def formatter_for(klass) # :nodoc: check_modules = true until klass.nil? formatter = @class_formatters[klass.name] diff --git a/lib/lumberjack/formatter/date_time_formatter.rb b/lib/lumberjack/formatter/date_time_formatter.rb index 88dc720..f10785c 100644 --- a/lib/lumberjack/formatter/date_time_formatter.rb +++ b/lib/lumberjack/formatter/date_time_formatter.rb @@ -7,6 +7,7 @@ class Formatter class DateTimeFormatter attr_reader :format + # @param [String] format The format to use when formatting the date/time object. def initialize(format = nil) @format = format.dup.to_s.freeze unless format.nil? end diff --git a/lib/lumberjack/formatter/exception_formatter.rb b/lib/lumberjack/formatter/exception_formatter.rb index 6a7cf24..0f1372c 100644 --- a/lib/lumberjack/formatter/exception_formatter.rb +++ b/lib/lumberjack/formatter/exception_formatter.rb @@ -9,6 +9,8 @@ class Formatter class ExceptionFormatter attr_accessor :backtrace_cleaner + # @param [#call] backtrace_cleaner An object that responds to `call` and takes + # an array of strings (the backtrace) and returns an array of strings (the def initialize(backtrace_cleaner = nil) self.backtrace_cleaner = backtrace_cleaner end diff --git a/lib/lumberjack/formatter/id_formatter.rb b/lib/lumberjack/formatter/id_formatter.rb index deaba7b..33121cd 100644 --- a/lib/lumberjack/formatter/id_formatter.rb +++ b/lib/lumberjack/formatter/id_formatter.rb @@ -6,6 +6,7 @@ class Formatter # as a default formatter for objects pulled from a data store. By default it will use :id as the # id attribute. class IdFormatter + # @param [Symbol, String] id_attribute The attribute to use as the id. def initialize(id_attribute = :id) @id_attribute = id_attribute end diff --git a/lib/lumberjack/formatter/pretty_print_formatter.rb b/lib/lumberjack/formatter/pretty_print_formatter.rb index 3a2cd1b..36af640 100644 --- a/lib/lumberjack/formatter/pretty_print_formatter.rb +++ b/lib/lumberjack/formatter/pretty_print_formatter.rb @@ -11,6 +11,8 @@ class PrettyPrintFormatter # Create a new formatter. The maximum width of the message can be specified with the width # parameter (defaults to 79 characters). + # + # @param [Integer] width The maximum width of the message. def initialize(width = 79) @width = width end diff --git a/lib/lumberjack/formatter/structured_formatter.rb b/lib/lumberjack/formatter/structured_formatter.rb index 92138dd..c6fe2b8 100644 --- a/lib/lumberjack/formatter/structured_formatter.rb +++ b/lib/lumberjack/formatter/structured_formatter.rb @@ -9,6 +9,8 @@ class StructuredFormatter class RecusiveReferenceError < StandardError end + # @param [Formatter] formatter The formatter to call on each element + # in the structure. def initialize(formatter = nil) @formatter = formatter end diff --git a/lib/lumberjack/formatter/truncate_formatter.rb b/lib/lumberjack/formatter/truncate_formatter.rb new file mode 100644 index 0000000..fb0baee --- /dev/null +++ b/lib/lumberjack/formatter/truncate_formatter.rb @@ -0,0 +1,27 @@ +# frozen_string_literals: true + +module Lumberjack + class Formatter + # Truncate a string object to a specific length. This is useful + # for formatting messages when there is a limit on the number of + # characters that can be logged per message. This formatter should + # only be used when necessary since it is a lossy formatter. + # + # When a string is truncated, it will have a unicode ellipsis + # character (U+2026) appended to the end of the string. + class TruncateFormatter + # @param [Integer] length The maximum length of the string (defaults to 32K) + def initialize(length = 32768) + @length = length + end + + def call(obj) + if obj.is_a?(String) && obj.length > @length + "#{obj[0, @length - 1]}…" + else + obj + end + end + end + end +end diff --git a/lib/lumberjack/log_entry.rb b/lib/lumberjack/log_entry.rb index 9094128..8e76dcc 100644 --- a/lib/lumberjack/log_entry.rb +++ b/lib/lumberjack/log_entry.rb @@ -10,6 +10,14 @@ class LogEntry UNIT_OF_WORK_ID = "unit_of_work_id" + # Create a new log entry. + # + # @param [Time] time The time the log entry was created. + # @param [Integer, String] severity The severity of the log entry. + # @param [String] message The message to log. + # @param [String] progname The name of the program that created the log entry. + # @param [Integer] pid The process id of the program that created the log entry. + # @param [Hash] tags A hash of tags to associate with the log entry. def initialize(time, severity, message, progname, pid, tags) @time = time @severity = (severity.is_a?(Integer) ? severity : Severity.label_to_level(severity)) diff --git a/lib/lumberjack/logger.rb b/lib/lumberjack/logger.rb index 37ffdbd..a994729 100644 --- a/lib/lumberjack/logger.rb +++ b/lib/lumberjack/logger.rb @@ -63,6 +63,9 @@ class Logger # * :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. + # + # @param [Lumberjack::Device, Object, Symbol, String] device The device to log to. + # @param [Hash] options The options for the logger. def initialize(device = $stdout, options = {}) options = options.dup self.level = options.delete(:level) || INFO @@ -83,11 +86,16 @@ def initialize(device = $stdout, options = {}) end # Get the timestamp format on the device if it has one. + # + # @return [String, nil] The timestamp format or nil if the device doesn't support it. def datetime_format device.datetime_format if device.respond_to?(:datetime_format) end # Set the timestamp format on the device if it is supported. + # + # @param [String] format The timestamp format. + # @return [void] def datetime_format=(format) if device.respond_to?(:datetime_format=) device.datetime_format = format @@ -96,14 +104,19 @@ def datetime_format=(format) # Get the level of severity of entries that are logged. Entries with a lower # severity level will be ignored. + # + # @return [Integer] The severity level. def level thread_local_value(:lumberjack_logger_level) || @level end - alias sev_threshold level + alias_method :sev_threshold, :level # Set the log level using either an integer level like Logger::INFO or a label like # :info or "info" + # + # @param [Integer, Symbol, String] value The severity level. + # @return [void] def level=(value) @level = if value.is_a?(Integer) value @@ -112,14 +125,19 @@ def level=(value) end end - alias sev_threshold= level= + alias_method :sev_threshold=, :level= # Set the Lumberjack::Formatter used to format objects for logging as messages. + # + # @param [Lumberjack::Formatter, Object] value The formatter to use. + # @return [void] def formatter=(value) @_formatter = (value.is_a?(TaggedLoggerSupport::Formatter) ? value.__formatter : value) end # Get the Lumberjack::Formatter used to format objects for logging as messages. + # + # @return [Lumberjack::Formatter] The formatter. def formatter if respond_to?(:tagged) # Wrap in an object that supports ActiveSupport::TaggedLogger API @@ -136,6 +154,8 @@ def formatter # The tags added with this method are just strings so they are stored in the logger tags # in an array under the "tagged" tag. So calling `logger.tagged("foo", "bar")` will result # in tags `{"tagged" => ["foo", "bar"]}`. + # + # @return [Lumberjack::Logger] self. def tagged_logger! extend(TaggedLoggerSupport) self @@ -149,7 +169,13 @@ def tagged_logger! # The severity can be passed in either as one of the Severity constants, # or as a Severity label. # - # === Example + # @param [Integer, Symbol, String] severity The severity of the message. + # @param [Object] message The message to log. + # @param [String] progname The name of the program that is logging the message. + # @param [Hash] tags The tags to add to the log entry. + # @return [void] + # + # @example # # logger.add_entry(Logger::ERROR, exception) # logger.add_entry(Logger::INFO, "Request completed") @@ -191,6 +217,11 @@ def add_entry(severity, message, progname = nil, tags = nil) end # ::Logger compatible method to add a log entry. + # + # @param [Integer, Symbol, String] severity The severity of the message. + # @param [Object] message The message to log. + # @param [String] progname The name of the program that is logging the message. + # @return [void] def add(severity, message = nil, progname = nil, &block) if message.nil? if block @@ -203,9 +234,11 @@ def add(severity, message = nil, progname = nil, &block) add_entry(severity, message, progname) end - alias log add + alias_method :log, :add # Flush the logging device. Messages are not guaranteed to be written until this method is called. + # + # @return [void] def flush device.flush @last_flushed_at = Time.now @@ -213,102 +246,170 @@ def flush end # Close the logging device. + # + # @return [void] def close flush device.close if device.respond_to?(:close) @closed = true end + # Returns +true+ if the logging device is closed. + # + # @return [Boolean] +true+ if the logging device is closed. def closed? @closed end + # Reopen the logging device. + # + # @param [Object] logdev passed through to the logging device. def reopen(logdev = nil) @closed = false device.reopen(logdev) if device.respond_to?(:reopen) end # Log a +FATAL+ message. The message can be passed in either the +message+ argument or in a block. + # + # @param [Object] message_or_progname_or_tags The message to log or progname + # if the message is passed in a block. + # @param [String, Hash] progname_or_tags The name of the program that is logging the message or tags + # if the message is passed in a block. + # @return [void] def fatal(message_or_progname_or_tags = nil, progname_or_tags = nil, &block) call_add_entry(FATAL, message_or_progname_or_tags, progname_or_tags, &block) end # Return +true+ if +FATAL+ messages are being logged. + # + # @return [Boolean] def fatal? level <= FATAL end # Set the log level to fatal. + # + # @return [void] def fatal! self.level = FATAL end # Log an +ERROR+ message. The message can be passed in either the +message+ argument or in a block. + # + # @param [Object] message_or_progname_or_tags The message to log or progname + # if the message is passed in a block. + # @param [String, Hash] progname_or_tags The name of the program that is logging the message or tags + # if the message is passed in a block. + # @return [void] def error(message_or_progname_or_tags = nil, progname_or_tags = nil, &block) call_add_entry(ERROR, message_or_progname_or_tags, progname_or_tags, &block) end # Return +true+ if +ERROR+ messages are being logged. + # + # @return [Boolean] def error? level <= ERROR end # Set the log level to error. + # + # @return [void] def error! self.level = ERROR end # Log a +WARN+ message. The message can be passed in either the +message+ argument or in a block. + # + # @param [Object] message_or_progname_or_tags The message to log or progname + # if the message is passed in a block. + # @param [String, Hash] progname_or_tags The name of the program that is logging the message or tags + # if the message is passed in a block. + # @return [void] def warn(message_or_progname_or_tags = nil, progname_or_tags = nil, &block) call_add_entry(WARN, message_or_progname_or_tags, progname_or_tags, &block) end # Return +true+ if +WARN+ messages are being logged. + # + # @return [Boolean] def warn? level <= WARN end # Set the log level to warn. + # + # @return [void] def warn! self.level = WARN end # Log an +INFO+ message. The message can be passed in either the +message+ argument or in a block. + # + # @param [Object] message_or_progname_or_tags The message to log or progname + # if the message is passed in a block. + # @param [String] progname_or_tags The name of the program that is logging the message or tags + # if the message is passed in a block. + # @return [void] def info(message_or_progname_or_tags = nil, progname_or_tags = nil, &block) call_add_entry(INFO, message_or_progname_or_tags, progname_or_tags, &block) end # Return +true+ if +INFO+ messages are being logged. + # + def info? level <= INFO end # Set the log level to info. + # + # @return [void] def info! self.level = INFO end # Log a +DEBUG+ message. The message can be passed in either the +message+ argument or in a block. + # + # @param [Object] message_or_progname_or_tags The message to log or progname + # if the message is passed in a block. + # @param [String, Hash] progname_or_tags The name of the program that is logging the message or tags + # if the message is passed in a block. + # @return [void] def debug(message_or_progname_or_tags = nil, progname_or_tags = nil, &block) call_add_entry(DEBUG, message_or_progname_or_tags, progname_or_tags, &block) end # Return +true+ if +DEBUG+ messages are being logged. + # + # @return [Boolean] def debug? level <= DEBUG end # Set the log level to debug. + # + # @return [void] def debug! self.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. + # + # @param [Object] message_or_progname_or_tags The message to log or progname + # if the message is passed in a block. + # @param [String, Hash] progname_or_tags The name of the program that is logging the message or tags + # if the message is passed in a block. + # @return [void] def unknown(message_or_progname_or_tags = nil, progname_or_tags = nil, &block) call_add_entry(UNKNOWN, message_or_progname_or_tags, progname_or_tags, &block) end + # Add a message when the severity is not known. + # + # @param [Object] msg The message to log. + # @return [void] def <<(msg) add_entry(UNKNOWN, msg) end @@ -316,7 +417,10 @@ def <<(msg) # Silence the logger by setting a new log level inside a block. By default, only +ERROR+ or +FATAL+ # messages will be logged. # - # === Example + # @param [Integer, String, Symbol] temporary_level The log level to use inside the block. + # @return [Object] The result of the block. + # + # @example # # logger.level = Logger::INFO # logger.silence do @@ -335,6 +439,9 @@ def silence(temporary_level = ERROR, &block) # 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. + # + # @param [String] value The program name to use. + # @return [void] def set_progname(value, &block) if block push_thread_local_value(:lumberjack_logger_progname, value, &block) @@ -344,6 +451,8 @@ def set_progname(value, &block) end # Get the program name associated with log messages. + # + # @return [String] def progname thread_local_value(:lumberjack_logger_progname) || @progname end @@ -353,6 +462,9 @@ def progname # the tags will only be defined on the tags in that block. When the parent block # exits, all the tags will be reverted. If there is no block, then the tags will # be defined as global and apply to all log statements. + # + # @param [Hash] tags The tags to set. + # @return [void] def tag(tags, &block) tags = Tags.stringify_keys(tags) thread_tags = thread_local_value(:lumberjack_logger_tags) @@ -371,6 +483,9 @@ def tag(tags, &block) # Remove a tag from the current tag context. If this is called inside a block to a # call to `tag`, the tags will only be removed for the duration of that block. Otherwise # they will be removed from the global tags. + # + # @param [Array] tag_names The tags to remove. + # @return [void] def remove_tag(*tag_names) thread_tags = thread_local_value(:lumberjack_logger_tags) if thread_tags @@ -382,6 +497,8 @@ def remove_tag(*tag_names) # Return all tags in scope on the logger including global tags set on the Lumberjack # context, tags set on the logger, and tags set on the current block for the logger. + # + # @return [Hash] def tags tags = {} context_tags = Lumberjack.context_tags @@ -395,6 +512,8 @@ def tags # Remove all tags on the current logger and logging context within a block. # You can still set new block scoped tags within theuntagged block and provide # tags on individual log methods. + # + # @return [void] def untagged(&block) Lumberjack.use_context(nil) do scope_tags = thread_local_value(:lumberjack_logger_tags) @@ -413,7 +532,7 @@ def untagged(&block) private # Dereference arguments to log calls so we can have methods with compatibility with ::Logger - def call_add_entry(severity, message_or_progname_or_tags, progname_or_tags, &block) #:nodoc: + def call_add_entry(severity, message_or_progname_or_tags, progname_or_tags, &block) # :nodoc: message = nil progname = nil tags = nil @@ -438,7 +557,7 @@ def call_add_entry(severity, message_or_progname_or_tags, progname_or_tags, &blo end # Set a local value for a thread tied to this object. - def set_thread_local_value(name, value) #:nodoc: + def set_thread_local_value(name, value) # :nodoc: values = Thread.current[name] unless values values = {} @@ -453,13 +572,13 @@ def set_thread_local_value(name, value) #:nodoc: end # Get a local value for a thread tied to this object. - def thread_local_value(name) #:nodoc: + 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: + def push_thread_local_value(name, value) # :nodoc: save_val = thread_local_value(name) set_thread_local_value(name, value) begin @@ -470,7 +589,7 @@ def push_thread_local_value(name, value) #:nodoc: end # Open a logging device. - def open_device(device, options) #:nodoc: + def open_device(device, options) # :nodoc: if device.nil? nil elsif device.is_a?(Device) @@ -491,7 +610,7 @@ def open_device(device, options) #:nodoc: end end - def write_to_device(entry) #:nodoc: + def write_to_device(entry) # :nodoc: device.write(entry) rescue => e # rubocop:disable Style/StderrPuts @@ -501,7 +620,7 @@ def write_to_device(entry) #:nodoc: end # Create a thread that will periodically call flush. - def create_flusher_thread(flush_seconds) #:nodoc: + def create_flusher_thread(flush_seconds) # :nodoc: if flush_seconds > 0 begin logger = self diff --git a/lib/lumberjack/severity.rb b/lib/lumberjack/severity.rb index 2f9bc1e..929c509 100644 --- a/lib/lumberjack/severity.rb +++ b/lib/lumberjack/severity.rb @@ -14,10 +14,18 @@ module Severity SEVERITY_LABELS = %w[DEBUG INFO WARN ERROR FATAL UNKNOWN].freeze class << self + # Convert a severity level to a label. + # + # @param [Integer] severity The severity level to convert. + # @return [String] The severity label. def level_to_label(severity) SEVERITY_LABELS[severity] || SEVERITY_LABELS.last end + # Convert a severity label to a level. + # + # @param [String, Symbol] label The severity label to convert. + # @return [Integer] The severity level. def label_to_level(label) SEVERITY_LABELS.index(label.to_s.upcase) || UNKNOWN end diff --git a/lib/lumberjack/tag_formatter.rb b/lib/lumberjack/tag_formatter.rb index c493838..2f18f72 100644 --- a/lib/lumberjack/tag_formatter.rb +++ b/lib/lumberjack/tag_formatter.rb @@ -16,6 +16,10 @@ def initialize # Add a default formatter applied to all tag values. This can either be a Lumberjack::Formatter # or an object that responds to `call` or a block. + # + # @param [Lumberjack::Formatter, #call, nil] formatter The formatter to use. + # If this is nil, then the block will be used as the formatter. + # @return [Lumberjack::TagFormatter] self def default(formatter = nil, &block) formatter ||= block formatter = dereference_formatter(formatter) @@ -24,6 +28,8 @@ def default(formatter = nil, &block) end # Remove the default formatter. + # + # @return [Lumberjack::TagFormatter] self def remove_default @default_formatter = nil self @@ -32,6 +38,11 @@ def remove_default # Add a formatter for specific tag names. This can either be a Lumberjack::Formatter # or an object that responds to `call` or a block. The default formatter will not be # applied. + # + # @param [String, Array] names The tag names to apply the formatter to. + # @param [Lumberjack::Formatter, #call, nil] formatter The formatter to use. + # If this is nil, then the block will be used as the formatter. + # @return [Lumberjack::TagFormatter] self def add(names, formatter = nil, &block) formatter ||= block formatter = dereference_formatter(formatter) @@ -46,6 +57,9 @@ def add(names, formatter = nil, &block) end # Remove formatters for specific tag names. The default formatter will still be applied. + # + # @param [String, Array] names The tag names to remove the formatter from. + # @return [Lumberjack::TagFormatter] self def remove(names) Array(names).each do |name| @formatters.delete(name.to_s) @@ -54,6 +68,8 @@ def remove(names) end # Remove all formatters. + # + # @return [Lumberjack::TagFormatter] self def clear @default_formatter = nil @formatters.clear @@ -61,6 +77,9 @@ def clear end # Format a hash of tags using the formatters + # + # @param [Hash] tags The tags to format. + # @return [Hash] The formatted tags. def format(tags) return nil if tags.nil? if @default_formatter.nil? && (@formatters.empty? || (@formatters.keys & tags.keys).empty?) diff --git a/lib/lumberjack/tagged_logger_support.rb b/lib/lumberjack/tagged_logger_support.rb index ff69cbb..9cb8530 100644 --- a/lib/lumberjack/tagged_logger_support.rb +++ b/lib/lumberjack/tagged_logger_support.rb @@ -55,7 +55,7 @@ def push_tags(*tags) def pop_tags(size = 1) tagged_values = Array(@tags["tagged"]) - tagged_values = (tagged_values.size > size ? tagged_values[0, tagged_values.size - size] : nil) + tagged_values = ((tagged_values.size > size) ? tagged_values[0, tagged_values.size - size] : nil) tag("tagged" => tagged_values) end diff --git a/lib/lumberjack/tags.rb b/lib/lumberjack/tags.rb index b282a45..90555a4 100644 --- a/lib/lumberjack/tags.rb +++ b/lib/lumberjack/tags.rb @@ -5,6 +5,9 @@ class Tags class << self # Transform hash keys to strings. This method exists for optimization and backward compatibility. # If a hash already has string keys, it will be returned as is. + # + # @param [Hash] hash The hash to transform. + # @return [Hash] The hash with string keys. def stringify_keys(hash) return nil if hash.nil? if hash.keys.all? { |key| key.is_a?(String) } @@ -22,6 +25,9 @@ def stringify_keys(hash) # Ensure keys are strings and expand any values in a hash that are Proc's by calling them and replacing # the value with the result. This allows setting global tags with runtime values. + # + # @param [Hash] hash The hash to transform. + # @return [Hash] The hash with string keys and expanded values. def expand_runtime_values(hash) return nil if hash.nil? if hash.all? { |key, value| key.is_a?(String) && !value.is_a?(Proc) } diff --git a/lib/lumberjack/template.rb b/lib/lumberjack/template.rb index cfc2b9c..842eb8d 100644 --- a/lib/lumberjack/template.rb +++ b/lib/lumberjack/template.rb @@ -30,6 +30,9 @@ class Template # specified precision. # # Messages will have white space stripped from both ends. + # + # @param [String] first_line The template to use to format the first line of a message. + # @param [Hash] options The options for the template. def initialize(first_line, options = {}) @first_line_template, @first_line_tags = compile(first_line) additional_lines = options[:additional_lines] || "#{Lumberjack::LINE_SEPARATOR}:message" @@ -39,6 +42,9 @@ def initialize(first_line, options = {}) self.datetime_format = (options[:time_format] || :milliseconds) end + # Set the format used to format the time. + # + # @param [String] format The format to use to format the time. def datetime_format=(format) if format == :milliseconds format = MILLISECOND_TIME_FORMAT @@ -48,11 +54,17 @@ def datetime_format=(format) @time_formatter = Formatter::DateTimeFormatter.new(format) end + # Get the format used to format the time. + # + # @return [String] def datetime_format @time_formatter.format end # Convert an entry into a string using the template. + # + # @param [Lumberjack::LogEntry] entry The entry to convert to a string. + # @return [String] The entry converted to a string. def call(entry) return entry unless entry.is_a?(LogEntry) @@ -103,7 +115,7 @@ def tag_args(tags, tag_vars) end # Compile the template string into a value that can be used with sprintf. - def compile(template) #:nodoc: + def compile(template) # :nodoc: tag_vars = [] template = template.gsub(PLACEHOLDER_PATTERN) do |match| var_name = match.sub("{", "").sub("}", "") diff --git a/spec/device/size_rolling_log_file_spec.rb b/spec/device/size_rolling_log_file_spec.rb index 1fb77a7..daedd6e 100644 --- a/spec/device/size_rolling_log_file_spec.rb +++ b/spec/device/size_rolling_log_file_spec.rb @@ -48,7 +48,7 @@ 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) } + File.write("#{log_file}.#{i}", i.to_s) end device = Lumberjack::Device::SizeRollingLogFile.new(log_file, max_size: "100M", min_roll_check: 0) expect(device.archive_file_suffix).to eq("12") diff --git a/spec/formatter/truncate_formatter_spec.rb b/spec/formatter/truncate_formatter_spec.rb new file mode 100644 index 0000000..193f8ed --- /dev/null +++ b/spec/formatter/truncate_formatter_spec.rb @@ -0,0 +1,18 @@ +require "spec_helper" + +describe Lumberjack::Formatter::TruncateFormatter do + it "should truncate a string longer than the limit" do + formatter = Lumberjack::Formatter::TruncateFormatter.new(9) + expect(formatter.call("1234567890")).to eq "12345678…" + end + + it "should not truncate a string that is shorter than the length" do + formatter = Lumberjack::Formatter::TruncateFormatter.new(9) + expect(formatter.call("123456789")).to eq "123456789" + end + + it "should pass through objects that are not strings" do + formatter = Lumberjack::Formatter::TruncateFormatter.new(9) + expect(formatter.call(:abcdefghijklmnop)).to eq :abcdefghijklmnop + end +end diff --git a/spec/formatter_spec.rb b/spec/formatter_spec.rb index 3e040b1..8ca18ab 100644 --- a/spec/formatter_spec.rb +++ b/spec/formatter_spec.rb @@ -25,6 +25,11 @@ expect(formatter.format(nil)).to eq(0) end + it "should be able to add a formatter with arguments" do + formatter.add(String, "Lumberjack::Formatter::TruncateFormatter", 9) + expect(formatter.format("1234567890")).to eq("12345678…") + end + it "should be able to add a formatter object for a module" do formatter.add(Enumerable, lambda { |obj| "list: #{obj.inspect}" }) expect(formatter.format([1, 2])).to eq("list: [1, 2]")