├── Gemfile ├── gemfiles ├── rails_5.2.6.gemfile ├── rails_6.0.4.gemfile ├── rails_6.1.4.gemfile └── rails_7.0.1.gemfile ├── Appraisals ├── .gitignore ├── .rubocop.yml ├── lib ├── syslogger │ └── version.rb └── syslogger.rb ├── Guardfile ├── Rakefile ├── CHANGELOG.md ├── bin ├── rake ├── guard ├── rspec ├── rubocop ├── _guard-core ├── appraisal └── bundle ├── spec ├── spec_helper.rb └── syslogger_spec.rb ├── LICENSE ├── syslogger.gemspec ├── .github └── workflows │ └── ci.yml └── README.md /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gemspec 6 | -------------------------------------------------------------------------------- /gemfiles/rails_5.2.6.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "activejob", "5.2.6" 6 | 7 | gemspec path: "../" 8 | -------------------------------------------------------------------------------- /gemfiles/rails_6.0.4.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "activejob", "6.0.4" 6 | 7 | gemspec path: "../" 8 | -------------------------------------------------------------------------------- /gemfiles/rails_6.1.4.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "activejob", "6.1.4" 6 | 7 | gemspec path: "../" 8 | -------------------------------------------------------------------------------- /gemfiles/rails_7.0.1.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "activejob", "7.0.1" 6 | 7 | gemspec path: "../" 8 | -------------------------------------------------------------------------------- /Appraisals: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RAILS_VERSIONS = %w[ 4 | 5.2.6 5 | 6.0.4 6 | 6.1.4 7 | 7.0.1 8 | ].freeze 9 | 10 | RAILS_VERSIONS.each do |version| 11 | appraise "rails_#{version}" do 12 | gem 'activejob', version 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore bundler config. 2 | /.bundle 3 | 4 | # Ignore Gemfile.lock 5 | /Gemfile.lock 6 | /gemfiles/*.lock 7 | /gemfiles/.bundle 8 | 9 | # Ignore test files 10 | /coverage 11 | /tmp 12 | 13 | # RVM files 14 | /.ruby-version 15 | 16 | # Gem files 17 | /*.gem 18 | 19 | # RDoc 20 | /rdoc 21 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | NewCops: enable 3 | SuggestExtensions: false 4 | Exclude: 5 | - bin/* 6 | - gemfiles/* 7 | - spec/**/* 8 | 9 | Metrics/LineLength: 10 | Enabled: false 11 | 12 | Layout/HashAlignment: 13 | EnforcedHashRocketStyle: table 14 | EnforcedColonStyle: table 15 | -------------------------------------------------------------------------------- /lib/syslogger/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Syslogger # :nodoc: 4 | def self.gem_version 5 | Gem::Version.new VERSION::STRING 6 | end 7 | 8 | module VERSION # :nodoc: 9 | MAJOR = 1 10 | MINOR = 6 11 | TINY = 6 12 | PRE = nil 13 | 14 | STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | guard :rspec, cmd: 'bundle exec rspec' do 4 | require 'guard/rspec/dsl' 5 | dsl = Guard::RSpec::Dsl.new(self) 6 | 7 | # RSpec files 8 | rspec = dsl.rspec 9 | watch(rspec.spec_helper) { rspec.spec_dir } 10 | watch(rspec.spec_support) { rspec.spec_dir } 11 | watch(rspec.spec_files) 12 | 13 | # Ruby files 14 | ruby = dsl.ruby 15 | dsl.watch_spec_files_for(ruby.lib_files) 16 | end 17 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/gem_tasks' 4 | require 'rspec/core/rake_task' 5 | require 'rdoc/task' 6 | 7 | RSpec::Core::RakeTask.new(:spec) 8 | task default: :spec 9 | 10 | Rake::RDocTask.new do |rdoc| 11 | require 'syslogger' 12 | rdoc.rdoc_dir = 'rdoc' 13 | rdoc.title = "syslogger #{Syslogger::VERSION::STRING}" 14 | rdoc.rdoc_files.include('README*') 15 | rdoc.rdoc_files.include('lib/**/*.rb') 16 | end 17 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## 1.6.6 4 | 5 | * Drop support of Ruby 2.3 6 | * Drop support of Ruby 2.4 7 | * Add support of Ruby 2.6 8 | * Add support of Ruby 2.7 9 | * Add support of Ruby 3.0 10 | * Drop support of Rails 5.1 11 | * Add support of Rails 6.0 12 | * Add support of Rails 6.1 13 | * Add binstubs to ease development 14 | * Migrate from Travis to Github Actions 15 | 16 | ## 1.6.5 17 | 18 | * Merge [Fixnum type is deprecated](https://github.com/crohr/syslogger/pull/42) (thanks thesmart) 19 | * Merge [Add #puts method to Syslogger](https://github.com/crohr/syslogger/pull/38) (thanks VuokkoVuorinnen) 20 | * Fix [Severity is passed as an array in the call to the formatter](https://github.com/crohr/syslogger/issues/35) 21 | * Save uniq tags in the thread 22 | * Improve tests 23 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rake' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("rake", "rake") 30 | -------------------------------------------------------------------------------- /bin/guard: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'guard' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("guard", "guard") 30 | -------------------------------------------------------------------------------- /bin/rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rspec' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("rspec-core", "rspec") 30 | -------------------------------------------------------------------------------- /bin/rubocop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rubocop' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("rubocop", "rubocop") 30 | -------------------------------------------------------------------------------- /bin/_guard-core: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application '_guard-core' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("guard", "_guard-core") 30 | -------------------------------------------------------------------------------- /bin/appraisal: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'appraisal' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("appraisal", "appraisal") 30 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | require 'rspec' 3 | require 'active_job' 4 | 5 | # Start Simplecov 6 | SimpleCov.start 7 | 8 | # Configure RSpec 9 | RSpec.configure do |config| 10 | config.color = true 11 | config.fail_fast = false 12 | 13 | config.order = :random 14 | Kernel.srand config.seed 15 | 16 | config.expect_with :rspec do |c| 17 | c.syntax = :expect 18 | end 19 | end 20 | 21 | # Module helper for unit tests 22 | module JobBuffer 23 | class << self 24 | def clear 25 | values.clear 26 | end 27 | 28 | def add(value) 29 | values << value 30 | end 31 | 32 | def values 33 | @values ||= [] 34 | end 35 | 36 | def last_value 37 | values.last 38 | end 39 | end 40 | end 41 | 42 | # Class helper for unit tests 43 | class HelloJob < ActiveJob::Base 44 | def perform(greeter = "David") 45 | JobBuffer.add("#{greeter} says hello") 46 | end 47 | end 48 | 49 | # Configure I18n otherwise it throws errors in syslog 50 | I18n.available_locales = [:en] 51 | 52 | # Load lib 53 | require 'syslogger' 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2010 Cyril Rohr, INRIA Rennes-Bretagne Atlantique 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /syslogger.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'lib/syslogger/version' 4 | 5 | Gem::Specification.new do |s| 6 | s.name = 'syslogger' 7 | s.version = Syslogger::VERSION::STRING 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ['Cyril Rohr'] 10 | s.email = ['cyril.rohr@gmail.com'] 11 | s.homepage = 'http://github.com/crohr/syslogger' 12 | s.summary = 'Dead simple Ruby Syslog logger' 13 | s.description = 'Same as SyslogLogger, but without the ridiculous number of dependencies and with the possibility to specify the syslog facility' 14 | s.license = 'MIT' 15 | s.metadata = { 16 | 'homepage_uri' => 'https://github.com/crohr/syslogger', 17 | 'changelog_uri' => 'https://github.com/crohr/syslogger/blob/master/CHANGELOG.md', 18 | 'source_code_uri' => 'https://github.com/crohr/syslogger', 19 | 'bug_tracker_uri' => 'https://github.com/crohr/syslogger/issues' 20 | } 21 | 22 | s.files = `git ls-files`.split("\n") 23 | 24 | s.add_development_dependency 'activejob' 25 | s.add_development_dependency 'appraisal' 26 | s.add_development_dependency 'guard-rspec' 27 | s.add_development_dependency 'rake' 28 | s.add_development_dependency 'rdoc' 29 | s.add_development_dependency 'rspec' 30 | s.add_development_dependency 'rubocop' 31 | s.add_development_dependency 'simplecov' 32 | end 33 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | 4 | on: 5 | - push 6 | - pull_request 7 | 8 | jobs: 9 | rspec: 10 | runs-on: ubuntu-20.04 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | ruby: 15 | - '3.0' 16 | - '2.7' 17 | - '2.6' 18 | rails: 19 | - rails_5.2.6 20 | - rails_6.0.4 21 | - rails_6.1.4 22 | - rails_7.0.1 23 | exclude: 24 | - ruby: '2.6' 25 | rails: rails_7.0.1 26 | - ruby: '3.0' 27 | rails: rails_5.2.6 28 | 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v2 32 | 33 | - name: Setup Ruby 34 | uses: ruby/setup-ruby@v1 35 | with: 36 | ruby-version: ${{ matrix.ruby }} 37 | 38 | - name: Setup Ruby cache 39 | uses: actions/cache@v2 40 | with: 41 | path: vendor/bundle 42 | key: ${{ runner.os }}-gems-${{ matrix.ruby }}-${{ matrix.rails }}-${{ hashFiles('**/Gemfile.lock') }} 43 | restore-keys: | 44 | ${{ runner.os }}-gems-${{ matrix.ruby }}-${{ matrix.rails }}- 45 | 46 | - name: Bundle 47 | env: 48 | RAILS_VERSION: ${{ matrix.rails }} 49 | BUNDLE_GEMFILE: gemfiles/${{ matrix.rails }}.gemfile 50 | run: | 51 | gem install bundler 52 | bundle config path vendor/bundle 53 | bundle install --jobs 4 --retry 3 54 | 55 | - name: RSpec 56 | env: 57 | RAILS_VERSION: ${{ matrix.rails }} 58 | BUNDLE_GEMFILE: gemfiles/${{ matrix.rails }}.gemfile 59 | run: bin/rake 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Syslogger 2 | 3 | [![GitHub license](https://img.shields.io/github/license/crohr/syslogger.svg)](https://github.com/crohr/syslogger/blob/master/LICENSE) 4 | [![GitHub release](https://img.shields.io/github/release/crohr/syslogger.svg)](https://github.com/crohr/syslogger/releases/latest) 5 | [![Gem](https://img.shields.io/gem/v/syslogger.svg)](https://rubygems.org/gems/syslogger) 6 | [![Gem](https://img.shields.io/gem/dtv/syslogger.svg)](https://rubygems.org/gems/syslogger) 7 | [![CI](https://github.com/crohr/syslogger/workflows/CI/badge.svg)](https://github.com/crohr/syslogger/actions) 8 | 9 | A drop-in replacement for the standard Logger Ruby library, that logs to the syslog instead of a log file. 10 | Contrary to the SyslogLogger library, you can specify the facility and the syslog options. 11 | 12 | ## Installation 13 | 14 | ```sh 15 | $ gem install syslogger 16 | ``` 17 | 18 | ## Usage 19 | 20 | ```ruby 21 | require 'syslogger' 22 | 23 | # Will send all messages to the local0 facility, adding the process id in the message 24 | logger = Syslogger.new("app_name", Syslog::LOG_PID, Syslog::LOG_LOCAL0) 25 | 26 | # Optionally split messages to the specified number of bytes 27 | logger.max_octets = 480 28 | 29 | # Send messages that are at least of the Logger::INFO level 30 | logger.level = Logger::INFO # use Logger levels 31 | 32 | logger.debug "will not appear" 33 | logger.info "will appear" 34 | logger.warn "will appear" 35 | ``` 36 | 37 | ## Contributions 38 | 39 | See . 40 | 41 | ## Copyright 42 | 43 | Copyright (c) 2010 Cyril Rohr, INRIA Rennes-Bretagne Atlantique. See LICENSE for details. 44 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'bundle' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "rubygems" 12 | 13 | m = Module.new do 14 | module_function 15 | 16 | def invoked_as_script? 17 | File.expand_path($0) == File.expand_path(__FILE__) 18 | end 19 | 20 | def env_var_version 21 | ENV["BUNDLER_VERSION"] 22 | end 23 | 24 | def cli_arg_version 25 | return unless invoked_as_script? # don't want to hijack other binstubs 26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` 27 | bundler_version = nil 28 | update_index = nil 29 | ARGV.each_with_index do |a, i| 30 | if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN 31 | bundler_version = a 32 | end 33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ 34 | bundler_version = $1 35 | update_index = i 36 | end 37 | bundler_version 38 | end 39 | 40 | def gemfile 41 | gemfile = ENV["BUNDLE_GEMFILE"] 42 | return gemfile if gemfile && !gemfile.empty? 43 | 44 | File.expand_path("../../Gemfile", __FILE__) 45 | end 46 | 47 | def lockfile 48 | lockfile = 49 | case File.basename(gemfile) 50 | when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) 51 | else "#{gemfile}.lock" 52 | end 53 | File.expand_path(lockfile) 54 | end 55 | 56 | def lockfile_version 57 | return unless File.file?(lockfile) 58 | lockfile_contents = File.read(lockfile) 59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ 60 | Regexp.last_match(1) 61 | end 62 | 63 | def bundler_version 64 | @bundler_version ||= 65 | env_var_version || cli_arg_version || 66 | lockfile_version 67 | end 68 | 69 | def bundler_requirement 70 | return "#{Gem::Requirement.default}.a" unless bundler_version 71 | 72 | bundler_gem_version = Gem::Version.new(bundler_version) 73 | 74 | requirement = bundler_gem_version.approximate_recommendation 75 | 76 | return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0") 77 | 78 | requirement += ".a" if bundler_gem_version.prerelease? 79 | 80 | requirement 81 | end 82 | 83 | def load_bundler! 84 | ENV["BUNDLE_GEMFILE"] ||= gemfile 85 | 86 | activate_bundler 87 | end 88 | 89 | def activate_bundler 90 | gem_error = activation_error_handling do 91 | gem "bundler", bundler_requirement 92 | end 93 | return if gem_error.nil? 94 | require_error = activation_error_handling do 95 | require "bundler/version" 96 | end 97 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) 98 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" 99 | exit 42 100 | end 101 | 102 | def activation_error_handling 103 | yield 104 | nil 105 | rescue StandardError, LoadError => e 106 | e 107 | end 108 | end 109 | 110 | m.load_bundler! 111 | 112 | if m.invoked_as_script? 113 | load Gem.bin_path("bundler", "bundle") 114 | end 115 | -------------------------------------------------------------------------------- /lib/syslogger.rb: -------------------------------------------------------------------------------- 1 | require 'forwardable' 2 | require 'syslog' 3 | require 'logger' 4 | 5 | # Syslogger is a drop-in replacement for the standard Logger Ruby library, that logs to the syslog instead of a log file. 6 | # 7 | # Contrary to the SyslogLogger library, you can specify the facility and the syslog options. 8 | # 9 | class Syslogger 10 | extend Forwardable 11 | 12 | MUTEX = Mutex.new # :nodoc: 13 | 14 | attr_reader :level, :options, :facility 15 | attr_accessor :ident, :formatter, :max_octets 16 | 17 | MAPPING = { # :nodoc: 18 | Logger::DEBUG => Syslog::LOG_DEBUG, 19 | Logger::INFO => Syslog::LOG_INFO, 20 | Logger::WARN => Syslog::LOG_WARNING, 21 | Logger::ERROR => Syslog::LOG_ERR, 22 | Logger::FATAL => Syslog::LOG_CRIT, 23 | Logger::UNKNOWN => Syslog::LOG_ALERT 24 | }.freeze 25 | 26 | LEVELS = %w[debug info warn error fatal unknown].freeze # :nodoc: 27 | 28 | # Initializes default options for the logger 29 | # 30 | # ident:: the name of your program [default=$0]. 31 | # 32 | # options:: syslog options [default=Syslog::LOG_PID | Syslog::LOG_CONS]. 33 | # 34 | # Correct values are: 35 | # LOG_CONS : writes the message on the console if an error occurs when sending the message; 36 | # LOG_NDELAY : no delay before sending the message; 37 | # LOG_PERROR : messages will also be written on STDERR; 38 | # LOG_PID : adds the process number to the message (just after the program name) 39 | # 40 | # facility:: the syslog facility [default=nil] 41 | # 42 | # Correct values include: 43 | # Syslog::LOG_DAEMON 44 | # Syslog::LOG_USER 45 | # Syslog::LOG_SYSLOG 46 | # Syslog::LOG_LOCAL2 47 | # Syslog::LOG_NEWS 48 | # etc. 49 | # 50 | # Usage: 51 | # logger = Syslogger.new("my_app", Syslog::LOG_PID | Syslog::LOG_CONS, Syslog::LOG_LOCAL0) 52 | # logger.level = Logger::INFO # use Logger levels 53 | # logger.warn "warning message" 54 | # logger.debug "debug message" 55 | # logger.info "my_subapp" { "Some lazily computed message" } 56 | # 57 | def initialize(ident = $PROGRAM_NAME, options = Syslog::LOG_PID | Syslog::LOG_CONS, facility = nil) 58 | @ident = ident 59 | @options = options || (Syslog::LOG_PID | Syslog::LOG_CONS) 60 | @facility = facility 61 | @level = Logger::INFO 62 | @formatter = SimpleFormatter.new 63 | end 64 | 65 | ## 66 | # :method: debug 67 | # 68 | # :call-seq: 69 | # debug(message) 70 | # 71 | # Log message 72 | # +message+:: the message string. 73 | 74 | ## 75 | # :method: info 76 | # 77 | # :call-seq: 78 | # info(message) 79 | # 80 | # Log message 81 | # +message+:: the message string. 82 | 83 | ## 84 | # :method: warn 85 | # 86 | # :call-seq: 87 | # warn(message) 88 | # 89 | # Log message 90 | # +message+:: the message string. 91 | 92 | ## 93 | # :method: error 94 | # 95 | # :call-seq: 96 | # error(message) 97 | # 98 | # Log message 99 | # +message+:: the message string. 100 | 101 | ## 102 | # :method: fatal 103 | # 104 | # :call-seq: 105 | # fatal(message) 106 | # 107 | # Log message 108 | # +message+:: the message string. 109 | 110 | ## 111 | # :method: unknown 112 | # 113 | # :call-seq: 114 | # unknown(message) 115 | # 116 | # Makes grease fly. 117 | # +message+:: the message string. 118 | 119 | LEVELS.each do |logger_method| 120 | # Accepting *args as message could be nil. 121 | # Default params not supported in ruby 1.8.7 122 | define_method logger_method.to_sym do |*args, &block| 123 | severity = Logger.const_get(logger_method.upcase) 124 | return true if level > severity 125 | 126 | add(severity, nil, args.first, &block) 127 | end 128 | 129 | next if logger_method == 'unknown'.freeze 130 | 131 | define_method "#{logger_method}?".to_sym do 132 | level <= Logger.const_get(logger_method.upcase) 133 | end 134 | end 135 | 136 | # Log a message at the Logger::INFO level. 137 | def write(msg) 138 | add(Logger::INFO, msg) 139 | end 140 | alias << write 141 | alias puts write 142 | 143 | # Low level method to add a message. 144 | # +severity+:: the level of the message. One of Logger::DEBUG, Logger::INFO, Logger::WARN, Logger::ERROR, Logger::FATAL, Logger::UNKNOWN 145 | # +message+:: the message string. 146 | # If nil, the method will call the block and use the result as the message string. 147 | # If both are nil or no block is given, it will use the progname as per the behaviour of both the standard Ruby logger, and the Rails BufferedLogger. 148 | # +progname+:: optionally, overwrite the program name that appears in the log message. 149 | def add(severity, message = nil, progname = nil, &block) 150 | if message.nil? && block.nil? && !progname.nil? 151 | message, progname = progname, nil 152 | end 153 | progname ||= @ident 154 | mask = Syslog::LOG_UPTO(MAPPING[level]) 155 | communication = message || block && block.call 156 | formatted_communication = clean(formatter.call(severity, Time.now, progname, communication)) 157 | 158 | # Call Syslog 159 | syslog_add(progname, severity, mask, formatted_communication) 160 | end 161 | 162 | # Sets the minimum level for messages to be written in the log. 163 | # +level+:: one of Logger::DEBUG, Logger::INFO, Logger::WARN, Logger::ERROR, Logger::FATAL, Logger::UNKNOWN 164 | def level=(level) 165 | @level = sanitize_level(level) 166 | end 167 | 168 | # Tagging code borrowed from ActiveSupport gem 169 | def tagged(*tags) 170 | formatter.tagged(*tags) { yield self } 171 | end 172 | 173 | def_delegators :formatter, :current_tags, :push_tags, :pop_tags, :clear_tags! 174 | 175 | protected 176 | 177 | def sanitize_level(new_level) # :nodoc: 178 | begin 179 | new_level = Logger.const_get(new_level.to_s.upcase) 180 | rescue => _e 181 | raise ArgumentError.new("Invalid logger level `#{new_level.inspect}`") 182 | end if new_level.is_a?(Symbol) 183 | 184 | unless new_level.is_a?(Integer) 185 | raise ArgumentError.new("Invalid logger level `#{new_level.inspect}`") 186 | end 187 | 188 | new_level 189 | end 190 | 191 | # Borrowed from SyslogLogger. 192 | def clean(message) # :nodoc: 193 | message = message.to_s.dup 194 | message.strip! # remove whitespace 195 | message.gsub!(/\n/, '\\n'.freeze) # escape newlines 196 | message.gsub!(/%/, '%%'.freeze) # syslog(3) freaks on % (printf) 197 | message.gsub!(/\e\[[^m]*m/, ''.freeze) # remove useless ansi color codes 198 | message 199 | end 200 | 201 | private 202 | 203 | # rubocop:disable Metrics/AbcSize, Metrics/MethodLength 204 | def syslog_add(progname, severity, mask, formatted_communication) 205 | MUTEX.synchronize do 206 | Syslog.open(progname, @options, @facility) do |s| 207 | s.mask = mask 208 | if max_octets 209 | buffer = '' 210 | formatted_communication.bytes do |byte| 211 | buffer.concat(byte) 212 | # if the last byte we added is potentially part of an escape, we'll go ahead and add another byte 213 | if buffer.bytesize >= max_octets && !['%'.ord, '\\'.ord].include?(byte) 214 | s.log(MAPPING[severity], buffer) 215 | buffer = '' 216 | end 217 | end 218 | s.log(MAPPING[severity], buffer) unless buffer.empty? 219 | else 220 | s.log(MAPPING[severity], formatted_communication) 221 | end 222 | end 223 | end 224 | end 225 | # rubocop:enable Metrics/AbcSize, Metrics/MethodLength 226 | 227 | # Borrowed from ActiveSupport. 228 | # See: https://github.com/rails/rails/blob/master/activesupport/lib/active_support/tagged_logging.rb 229 | class SimpleFormatter < Logger::Formatter # :nodoc: 230 | # This method is invoked when a log event occurs. 231 | def call(_severity, _timestamp, _progname, msg) 232 | "#{tags_text}#{msg}" 233 | end 234 | 235 | def tagged(*tags) 236 | new_tags = push_tags(*tags) 237 | yield self 238 | ensure 239 | pop_tags(new_tags.size) 240 | end 241 | 242 | def push_tags(*tags) 243 | tags.flatten.reject { |i| i.respond_to?(:empty?) ? i.empty? : !i }.tap do |new_tags| 244 | current_tags.concat(new_tags).uniq! 245 | end 246 | end 247 | 248 | def pop_tags(size = 1) 249 | current_tags.pop size 250 | end 251 | 252 | def clear_tags! 253 | current_tags.clear 254 | end 255 | 256 | # Fix: https://github.com/crohr/syslogger/issues/29 257 | # See: https://github.com/rails/rails/blob/master/activesupport/lib/active_support/tagged_logging.rb#L47 258 | def current_tags 259 | # We use our object ID here to avoid conflicting with other instances 260 | thread_key = @thread_key ||= "syslogger_tagged_logging_tags:#{object_id}".freeze 261 | Thread.current[thread_key] ||= [] 262 | end 263 | 264 | def tags_text 265 | tags = current_tags 266 | tags.collect { |tag| "[#{tag}] " }.join if tags.any? 267 | end 268 | end 269 | end 270 | -------------------------------------------------------------------------------- /spec/syslogger_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Syslogger do 4 | 5 | let(:fake_syslog) { double('syslog', :mask= => true) } 6 | 7 | describe '.new' do 8 | it 'should log to the default syslog facility, with the default options' do 9 | expect(Syslog).to receive(:open).with($0, Syslog::LOG_PID | Syslog::LOG_CONS, nil).and_yield(fake_syslog) 10 | expect(fake_syslog).to receive(:log).with(Syslog::LOG_WARNING, 'Some message') 11 | logger = Syslogger.new 12 | logger.warn 'Some message' 13 | end 14 | 15 | it 'should log to the user facility, with specific options' do 16 | expect(Syslog).to receive(:open).with('my_app', Syslog::LOG_PID, Syslog::LOG_USER).and_yield(fake_syslog) 17 | expect(fake_syslog).to receive(:log).with(Syslog::LOG_WARNING, 'Some message') 18 | logger = Syslogger.new('my_app', Syslog::LOG_PID, Syslog::LOG_USER) 19 | logger.warn 'Some message' 20 | end 21 | end 22 | 23 | describe '#add' do 24 | let(:logger) { Syslogger.new('my_app', Syslog::LOG_PID, Syslog::LOG_USER) } 25 | 26 | it 'should respond to add' do 27 | expect(logger).to respond_to(:add) 28 | end 29 | 30 | it 'should correctly log' do 31 | expect(Syslog).to receive(:open).with('my_app', Syslog::LOG_PID, Syslog::LOG_USER).and_yield(fake_syslog) 32 | expect(fake_syslog).to receive(:log).with(Syslog::LOG_INFO, 'message') 33 | logger.add(Logger::INFO, 'message') 34 | end 35 | 36 | it 'should take the message from the block if :message is nil' do 37 | expect(Syslog).to receive(:open).with('my_app', Syslog::LOG_PID, Syslog::LOG_USER).and_yield(fake_syslog) 38 | expect(fake_syslog).to receive(:log).with(Syslog::LOG_INFO, 'message') 39 | logger.add(Logger::INFO) { 'message' } 40 | end 41 | 42 | it 'should use the given progname' do 43 | expect(Syslog).to receive(:open).with('progname', Syslog::LOG_PID, Syslog::LOG_USER).and_yield(fake_syslog) 44 | expect(fake_syslog).to receive(:log).with(Syslog::LOG_INFO, 'message') 45 | logger.add(Logger::INFO, 'message', 'progname') 46 | end 47 | 48 | it 'should use the default progname when message is passed in progname' do 49 | expect(Syslog).to receive(:open).with('my_app', Syslog::LOG_PID, Syslog::LOG_USER).and_yield(fake_syslog) 50 | expect(fake_syslog).to receive(:log).with(Syslog::LOG_INFO, 'message') 51 | logger.add(Logger::INFO, nil, 'message') 52 | end 53 | 54 | it 'should use the given progname if message is passed in block' do 55 | expect(Syslog).to receive(:open).with('progname', Syslog::LOG_PID, Syslog::LOG_USER).and_yield(fake_syslog) 56 | expect(fake_syslog).to receive(:log).with(Syslog::LOG_INFO, 'message') 57 | logger.add(Logger::INFO, nil, 'progname') { 'message' } 58 | end 59 | 60 | it "should substitute '%' for '%%' before adding the :message" do 61 | allow(Syslog).to receive(:open).and_yield(fake_syslog) 62 | expect(fake_syslog).to receive(:log).with(Syslog::LOG_INFO, "%%me%%ssage%%") 63 | logger.add(Logger::INFO, "%me%ssage%") 64 | end 65 | 66 | it 'should clean formatted message' do 67 | formatter = Class.new(Syslogger::SimpleFormatter) do 68 | def call(severity, timestamp, progname, msg) 69 | msg.split(//).join('%') 70 | end 71 | end 72 | 73 | allow(Syslog).to receive(:open).and_yield(fake_syslog) 74 | expect(fake_syslog).to receive(:log).with(Syslog::LOG_INFO, "m%%e%%s%%s%%a%%g%%e") 75 | 76 | original_formatter = logger.formatter 77 | 78 | begin 79 | logger.formatter = formatter.new 80 | logger.add(Logger::INFO, 'message') 81 | ensure 82 | logger.formatter = original_formatter 83 | end 84 | end 85 | 86 | it 'should clean tagged message' do 87 | allow(Syslog).to receive(:open).and_yield(fake_syslog) 88 | expect(fake_syslog).to receive(:log).with(Syslog::LOG_INFO, "[t%%a%%g%%g%%e%%d] [it] message") 89 | 90 | logger.tagged("t%a%g%g%e%d") do 91 | logger.tagged('it') do 92 | logger.add(Logger::INFO, 'message') 93 | end 94 | end 95 | end 96 | 97 | it 'should strip the :message' do 98 | allow(Syslog).to receive(:open).and_yield(fake_syslog) 99 | expect(fake_syslog).to receive(:log).with(Syslog::LOG_INFO, 'message') 100 | logger.add(Logger::INFO, "\n\nmessage ") 101 | end 102 | 103 | it 'should not raise exception if asked to log with a nil message and body' do 104 | expect(Syslog).to receive(:open).with('my_app', Syslog::LOG_PID, Syslog::LOG_USER).and_yield(fake_syslog) 105 | expect(fake_syslog).to receive(:log).with(Syslog::LOG_INFO, '') 106 | expect { 107 | logger.add(Logger::INFO, nil) 108 | }.to_not raise_error 109 | end 110 | 111 | it 'should send an empty string if the message and block are nil' do 112 | expect(Syslog).to receive(:open).with('my_app', Syslog::LOG_PID, Syslog::LOG_USER).and_yield(fake_syslog) 113 | expect(fake_syslog).to receive(:log).with(Syslog::LOG_INFO, '') 114 | logger.add(Logger::INFO, nil) 115 | end 116 | 117 | it 'should split string over the max octet size' do 118 | logger.max_octets = 480 119 | expect(Syslog).to receive(:open).with('my_app', Syslog::LOG_PID, Syslog::LOG_USER).and_yield(fake_syslog) 120 | expect(fake_syslog).to receive(:log).with(Syslog::LOG_INFO, 'a' * 480).twice 121 | logger.add(Logger::INFO, 'a' * 960) 122 | end 123 | 124 | it 'should apply the log formatter to the message' do 125 | formatter = Class.new(Syslogger::SimpleFormatter) do 126 | def call(severity, timestamp, progname, msg) 127 | "test #{msg}!" 128 | end 129 | end 130 | allow(Syslog).to receive(:open).and_yield(fake_syslog) 131 | expect(fake_syslog).to receive(:log).with(Syslog::LOG_INFO, 'test message!') 132 | logger.formatter = formatter.new 133 | logger.add(Logger::INFO, 'message') 134 | end 135 | end 136 | 137 | describe '#<<' do 138 | let(:logger) { Syslogger.new('my_app', Syslog::LOG_PID, Syslog::LOG_USER) } 139 | 140 | it 'should respond to <<' do 141 | expect(logger).to respond_to(:<<) 142 | end 143 | 144 | it 'should correctly log' do 145 | expect(Syslog).to receive(:open).with('my_app', Syslog::LOG_PID, Syslog::LOG_USER).and_yield(fake_syslog) 146 | expect(fake_syslog).to receive(:log).with(Syslog::LOG_INFO, 'message') 147 | logger << 'message' 148 | end 149 | end 150 | 151 | describe '#puts' do 152 | let(:logger) { Syslogger.new('my_app', Syslog::LOG_PID, Syslog::LOG_USER) } 153 | 154 | it 'should respond to puts' do 155 | expect(logger).to respond_to(:puts) 156 | end 157 | 158 | it 'should correctly log' do 159 | expect(Syslog).to receive(:open).with('my_app', Syslog::LOG_PID, Syslog::LOG_USER).and_yield(fake_syslog) 160 | expect(fake_syslog).to receive(:log).with(Syslog::LOG_INFO, 'message') 161 | logger.puts 'message' 162 | end 163 | end 164 | 165 | describe '#write' do 166 | let(:logger) { Syslogger.new('my_app', Syslog::LOG_PID, Syslog::LOG_USER) } 167 | 168 | it 'should respond to write' do 169 | expect(logger).to respond_to(:write) 170 | end 171 | 172 | it 'should correctly log' do 173 | expect(Syslog).to receive(:open).with('my_app', Syslog::LOG_PID, Syslog::LOG_USER).and_yield(fake_syslog) 174 | expect(fake_syslog).to receive(:log).with(Syslog::LOG_INFO, 'message') 175 | logger.write 'message' 176 | end 177 | end 178 | 179 | describe '#max_octets=' do 180 | let(:logger) { Syslogger.new('my_app', Syslog::LOG_PID, Syslog::LOG_USER) } 181 | 182 | it 'should set the max_octets for the logger' do 183 | expect { logger.max_octets = 1 }.to change(logger, :max_octets) 184 | expect(logger.max_octets).to eq 1 185 | end 186 | end 187 | 188 | describe '#ident=' do 189 | let(:logger) { Syslogger.new('my_app', Syslog::LOG_PID | Syslog::LOG_CONS, nil) } 190 | 191 | it 'should permanently change the ident string' do 192 | logger.ident = 'new_ident' 193 | expect(Syslog).to receive(:open).with('new_ident', Syslog::LOG_PID | Syslog::LOG_CONS, nil).and_yield(fake_syslog) 194 | expect(fake_syslog).to receive(:log) 195 | logger.warn('should get the new ident string') 196 | end 197 | end 198 | 199 | describe '#level=' do 200 | let(:logger) { Syslogger.new('my_app', Syslog::LOG_PID, Syslog::LOG_USER) } 201 | 202 | { :debug => Logger::DEBUG, 203 | :info => Logger::INFO, 204 | :warn => Logger::WARN, 205 | :error => Logger::ERROR, 206 | :fatal => Logger::FATAL 207 | }.each_pair do |level_symbol, level_value| 208 | it "should allow using :#{level_symbol}" do 209 | logger.level = level_symbol 210 | expect(logger.level).to eq level_value 211 | end 212 | 213 | it "should allow using Integer #{level_value}" do 214 | logger.level = level_value 215 | expect(logger.level).to eq level_value 216 | end 217 | end 218 | 219 | it 'should not allow using random symbols' do 220 | expect { 221 | logger.level = :foo 222 | }.to raise_error(ArgumentError) 223 | end 224 | 225 | it 'should not allow using symbols mapping back to non-level constants' do 226 | expect { 227 | logger.level = :version 228 | }.to raise_error(ArgumentError) 229 | end 230 | 231 | it 'should not allow using strings' do 232 | expect { 233 | logger.level = 'warn' 234 | }.to raise_error(ArgumentError) 235 | end 236 | end 237 | 238 | describe '#:level? methods' do 239 | let(:logger) { Syslogger.new('my_app', Syslog::LOG_PID, Syslog::LOG_USER) } 240 | 241 | %w{debug info warn error fatal}.each do |logger_method| 242 | it "should respond to the #{logger_method}? method" do 243 | expect(logger).to respond_to "#{logger_method}?".to_sym 244 | end 245 | end 246 | 247 | it 'should not have unknown? method' do 248 | expect(logger).to_not respond_to :unknown? 249 | end 250 | 251 | context 'when loglevel is Logger::DEBUG' do 252 | it 'should return true for all methods' do 253 | logger.level = Logger::DEBUG 254 | %w{debug info warn error fatal}.each do |logger_method| 255 | expect(logger.send("#{logger_method}?")).to be true 256 | end 257 | end 258 | end 259 | 260 | context 'when loglevel is Logger::INFO' do 261 | it 'should return true for all except debug?' do 262 | logger.level = Logger::INFO 263 | %w{info warn error fatal}.each do |logger_method| 264 | expect(logger.send("#{logger_method}?")).to be true 265 | end 266 | expect(logger.debug?).to be false 267 | end 268 | end 269 | 270 | context 'when loglevel is Logger::WARN' do 271 | it 'should return true for warn?, error? and fatal? when WARN' do 272 | logger.level = Logger::WARN 273 | %w{warn error fatal}.each do |logger_method| 274 | expect(logger.send("#{logger_method}?")).to be true 275 | end 276 | %w{debug info}.each do |logger_method| 277 | expect(logger.send("#{logger_method}?")).to be false 278 | end 279 | end 280 | end 281 | 282 | context 'when loglevel is Logger::ERROR' do 283 | it 'should return true for error? and fatal? when ERROR' do 284 | logger.level = Logger::ERROR 285 | %w{error fatal}.each do |logger_method| 286 | expect(logger.send("#{logger_method}?")).to be true 287 | end 288 | %w{warn debug info}.each do |logger_method| 289 | expect(logger.send("#{logger_method}?")).to be false 290 | end 291 | end 292 | end 293 | 294 | context 'when loglevel is Logger::FATAL' do 295 | it 'should return true only for fatal? when FATAL' do 296 | logger.level = Logger::FATAL 297 | expect(logger.fatal?).to be true 298 | %w{error warn debug info}.each do |logger_method| 299 | expect(logger.send("#{logger_method}?")).to be false 300 | end 301 | end 302 | end 303 | end 304 | 305 | # Fix https://github.com/crohr/syslogger/issues/29 306 | describe '#formatter' do 307 | let(:logger) { Syslogger.new('my_app', Syslog::LOG_PID, Syslog::LOG_USER) } 308 | 309 | it 'should not raise error' do 310 | ActiveJob::Base.logger = logger 311 | expect(logger).to receive(:info).at_least(1).times.with(nil) 312 | expect { 313 | HelloJob.perform_later "Cristian" 314 | }.to_not raise_error 315 | end 316 | end 317 | 318 | describe '#push_tags' do 319 | let(:logger) { Syslogger.new('my_app', Syslog::LOG_PID, Syslog::LOG_USER) } 320 | after(:each) { logger.clear_tags! } 321 | 322 | it 'saves tags' do 323 | logger.push_tags('tag1') 324 | logger.push_tags('tag2') 325 | expect(logger.current_tags).to eq ['tag1', 'tag2'] 326 | end 327 | 328 | it 'saves uniq tags' do 329 | logger.push_tags('tag1') 330 | logger.push_tags('tag2') 331 | logger.push_tags('foo') 332 | logger.push_tags('foo') 333 | expect(logger.current_tags).to eq ['tag1', 'tag2', 'foo'] 334 | end 335 | end 336 | 337 | describe '#clear_tags!' do 338 | let(:logger) { Syslogger.new('my_app', Syslog::LOG_PID, Syslog::LOG_USER) } 339 | after(:each) { logger.clear_tags! } 340 | 341 | it 'clears tags' do 342 | expect(logger.current_tags).to eq [] 343 | logger.push_tags('tag1') 344 | logger.push_tags('tag2') 345 | expect(logger.current_tags).to eq ['tag1', 'tag2'] 346 | logger.clear_tags! 347 | expect(logger.current_tags).to eq [] 348 | end 349 | end 350 | 351 | describe 'logger methods (debug info warn error fatal unknown)' do 352 | %w{debug info warn error fatal unknown}.each do |logger_method| 353 | 354 | it "should respond to the #{logger_method.inspect} method" do 355 | expect(Syslogger.new).to respond_to logger_method.to_sym 356 | end 357 | 358 | it "should log #{logger_method} without raising an exception if called with a block" do 359 | severity = Syslogger::MAPPING[Logger.const_get(logger_method.upcase)] 360 | 361 | logger = Syslogger.new 362 | logger.level = Logger.const_get(logger_method.upcase) 363 | 364 | expect(Syslog).to receive(:open).and_yield(fake_syslog) 365 | expect(fake_syslog).to receive(:log).with(severity, 'Some message that dont need to be in a block') 366 | expect { 367 | logger.send(logger_method.to_sym) { 'Some message that dont need to be in a block' } 368 | }.to_not raise_error 369 | end 370 | 371 | it "should log #{logger_method} using message as progname with the block's result" do 372 | severity = Syslogger::MAPPING[Logger.const_get(logger_method.upcase)] 373 | 374 | logger = Syslogger.new 375 | logger.level = Logger.const_get(logger_method.upcase) 376 | 377 | expect(Syslog).to receive(:open).with('Woah', anything, nil).and_yield(fake_syslog) 378 | expect(fake_syslog).to receive(:log).with(severity, 'Some message that really needs a block') 379 | expect { 380 | logger.send(logger_method.to_sym, 'Woah') { 'Some message that really needs a block' } 381 | }.to_not raise_error 382 | end 383 | 384 | it "should log #{logger_method} without raising an exception if called with a nil message" do 385 | logger = Syslogger.new 386 | expect { 387 | logger.send(logger_method.to_sym, nil) 388 | }.to_not raise_error 389 | end 390 | 391 | it "should log #{logger_method} without raising an exception if called with a no message" do 392 | logger = Syslogger.new 393 | expect { 394 | logger.send(logger_method.to_sym) 395 | }.to_not raise_error 396 | end 397 | 398 | it "should log #{logger_method} without raising an exception if message splits on an escape" do 399 | logger = Syslogger.new 400 | logger.max_octets = 100 401 | msg = 'A' * 99 402 | msg += "%BBB" 403 | expect { 404 | logger.send(logger_method.to_sym, msg) 405 | }.to_not raise_error 406 | end 407 | end 408 | end 409 | 410 | describe 'it should be thread safe' do 411 | it 'should not fail under chaos' do 412 | threads = [] 413 | (1..10).each do 414 | threads << Thread.new do 415 | (1..100).each do |index| 416 | logger = Syslogger.new(Thread.current.inspect, Syslog::LOG_PID, Syslog::LOG_USER) 417 | logger.write index 418 | end 419 | end 420 | end 421 | 422 | threads.map(&:join) 423 | end 424 | 425 | it 'should allow multiple instances to log at the same time' do 426 | logger1 = Syslogger.new('my_app1', Syslog::LOG_PID, Syslog::LOG_USER) 427 | logger2 = Syslogger.new('my_app2', Syslog::LOG_PID, Syslog::LOG_USER) 428 | 429 | syslog1 = double('syslog1', :mask= => true) 430 | syslog2 = double('syslog2', :mask= => true) 431 | 432 | expect(Syslog).to receive(:open).exactly(5000).times.with('my_app1', Syslog::LOG_PID, Syslog::LOG_USER).and_yield(syslog1) 433 | expect(Syslog).to receive(:open).exactly(5000).times.with('my_app2', Syslog::LOG_PID, Syslog::LOG_USER).and_yield(syslog2) 434 | 435 | expect(syslog1).to receive(:log).exactly(5000).times.with(Syslog::LOG_INFO, 'logger1') 436 | expect(syslog2).to receive(:log).exactly(5000).times.with(Syslog::LOG_INFO, 'logger2') 437 | 438 | threads = [] 439 | 440 | threads << Thread.new do 441 | 5000.times do |i| 442 | logger1.info 'logger1' 443 | end 444 | end 445 | 446 | threads << Thread.new do 447 | 5000.times do |i| 448 | logger2.info 'logger2' 449 | end 450 | end 451 | 452 | threads.map(&:join) 453 | end 454 | end 455 | 456 | describe 'it should respect loglevel precedence when logging' do 457 | %w{debug info warn error}.each do |logger_method| 458 | it "should not log #{logger_method} when level is higher" do 459 | logger = Syslogger.new 460 | logger.level = Logger::FATAL 461 | expect(Syslog).to_not receive(:open).with($0, Syslog::LOG_PID | Syslog::LOG_CONS, nil).and_yield(fake_syslog) 462 | expect(fake_syslog).to_not receive(:log).with(Syslog::LOG_NOTICE, 'Some message') 463 | logger.send(logger_method.to_sym, 'Some message') 464 | end 465 | 466 | it "should not evaluate a block or log #{logger_method} when level is higher" do 467 | logger = Syslogger.new 468 | logger.level = Logger::FATAL 469 | expect(Syslog).to_not receive(:open).with($0, Syslog::LOG_PID | Syslog::LOG_CONS, nil).and_yield(fake_syslog) 470 | expect(fake_syslog).to_not receive(:log).with(Syslog::LOG_NOTICE, 'Some message') 471 | logger.send(logger_method.to_sym) { violated 'This block should not have been called' } 472 | end 473 | end 474 | end 475 | end 476 | --------------------------------------------------------------------------------