[DRE-commits] [ruby-yell] 01/03: Imported Upstream version 2.0.5
Michael Crusoe
misterc-guest at moszumanska.debian.org
Wed Sep 30 06:37:09 UTC 2015
This is an automated email from the git hooks/post-receive script.
misterc-guest pushed a commit to branch master
in repository ruby-yell.
commit ea93ca102be93d294e34f896838d7e63393bb900
Author: Michael R. Crusoe <michael.crusoe at gmail.com>
Date: Sun Sep 20 00:05:57 2015 -0700
Imported Upstream version 2.0.5
---
.gitignore | 13 +
.travis.yml | 18 ++
Gemfile | 26 ++
LICENSE.txt | 21 ++
README.md | 219 +++++++++++++++++
Rakefile | 14 ++
examples/001-basic-usage.rb | 25 ++
examples/002.1-log-level-basics.rb | 25 ++
.../002.2-log-level-on-certain-severities-only.rb | 28 +++
examples/002.3-log-level-within-range.rb | 28 +++
examples/003.1-formatting-DefaultFormat.rb | 22 ++
examples/003.2-formatting-BasicFormat.rb | 22 ++
examples/003.3-formatting-ExtendedFormat.rb | 21 ++
examples/003.4-formatting-on-your-own.rb | 21 ++
examples/004.1-colorizing-the-log-output.rb | 23 ++
examples/005.1-repository.rb | 21 ++
examples/006.1-the-loggable-module.rb | 37 +++
.../006.2-the-loggable-module-with-inheritance.rb | 41 ++++
lib/core_ext/logger.rb | 18 ++
lib/yell.rb | 151 ++++++++++++
lib/yell/adapters.rb | 88 +++++++
lib/yell/adapters/base.rb | 231 ++++++++++++++++++
lib/yell/adapters/datefile.rb | 194 +++++++++++++++
lib/yell/adapters/file.rb | 36 +++
lib/yell/adapters/io.rb | 102 ++++++++
lib/yell/adapters/streams.rb | 32 +++
lib/yell/configuration.rb | 25 ++
lib/yell/event.rb | 130 ++++++++++
lib/yell/formatter.rb | 242 +++++++++++++++++++
lib/yell/helpers/adapter.rb | 46 ++++
lib/yell/helpers/base.rb | 19 ++
lib/yell/helpers/formatter.rb | 32 +++
lib/yell/helpers/level.rb | 40 +++
lib/yell/helpers/silencer.rb | 31 +++
lib/yell/helpers/tracer.rb | 48 ++++
lib/yell/level.rb | 214 ++++++++++++++++
lib/yell/loggable.rb | 37 +++
lib/yell/logger.rb | 163 +++++++++++++
lib/yell/repository.rb | 72 ++++++
lib/yell/silencer.rb | 86 +++++++
lib/yell/version.rb | 7 +
metadata.yml | 130 ++++++++++
spec/compatibility/activesupport_logger_spec.rb | 35 +++
spec/compatibility/formatter_spec.rb | 23 ++
spec/compatibility/level_spec.rb | 18 ++
spec/fixtures/yell.yml | 7 +
spec/spec_helper.rb | 56 +++++
spec/threaded/yell_spec.rb | 101 ++++++++
spec/yell/adapters/base_spec.rb | 43 ++++
spec/yell/adapters/datefile_spec.rb | 168 +++++++++++++
spec/yell/adapters/file_spec.rb | 75 ++++++
spec/yell/adapters/io_spec.rb | 72 ++++++
spec/yell/adapters/streams_spec.rb | 26 ++
spec/yell/adapters_spec.rb | 45 ++++
spec/yell/configuration_spec.rb | 36 +++
spec/yell/dsl_spec.rb | 49 ++++
spec/yell/event_spec.rb | 97 ++++++++
spec/yell/formatter_spec.rb | 146 +++++++++++
spec/yell/level_spec.rb | 200 +++++++++++++++
spec/yell/loggable_spec.rb | 20 ++
spec/yell/logger_spec.rb | 268 +++++++++++++++++++++
spec/yell/repository_spec.rb | 70 ++++++
spec/yell/silencer_spec.rb | 38 +++
spec/yell_spec.rb | 102 ++++++++
yell.gemspec | 23 ++
65 files changed, 4517 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..effd518
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+pkg/*
+*.gem
+.bundle
+.idea
+
+# bundler
+Gemfile.lock
+
+# vim
+*.swp
+
+# coverage
+/coverage
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..1a83dfd
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,18 @@
+language: ruby
+
+script: "rspec"
+
+rvm:
+ - 1.8.7
+ - 1.9.3
+ - 2.0.0
+ - 2.1.1
+ - jruby-18mode
+ - jruby-19mode
+ - rbx-2
+ - ree
+
+notifications:
+ email:
+ - me at rudionrails.com
+
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..0f588d4
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,26 @@
+source "http://rubygems.org"
+
+# Specify your gem's dependencies in yell.gemspec
+gemspec
+
+group :development, :test do
+ gem "rake"
+
+ gem 'rspec-core', '~> 2'
+ gem 'rspec-expectations', '~> 2'
+ gem "rr"
+
+ if RUBY_VERSION < "1.9"
+ gem 'timecop', '0.6.0'
+ gem 'activesupport', '~> 3'
+ else
+ gem 'timecop'
+ gem 'activesupport'
+
+ gem 'pry'
+ end
+
+ gem 'simplecov', :require => false, :platform => :ruby_20
+ gem 'coveralls', :require => false, :platform => :ruby_20
+end
+
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..885a9a8
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,21 @@
+Copyright (c) 2011-2014 Rudolf Schmidt
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..602e0ee
--- /dev/null
+++ b/README.md
@@ -0,0 +1,219 @@
+# Yell [](http://badge.fury.io/rb/yell) [](https://travis-ci.org/rudionrails/yell) [](https://codeclimate.com/github/rudionrails/yell) [](https://coveralls.io/r/rudionrails/yell)
+
+
+**Yell - Your Extensible Logging Library** is a comprehensive logging replacement for Ruby.
+
+Yell works and is tested with ruby 1.8.7, 1.9.x, 2.0.0, jruby 1.8 and 1.9 mode, rubinius 1.8 and 1.9 as well as ree.
+
+If you want to conveniently use Yell with Rails, then head over to [yell-rails](https://github.com/rudionrails/yell-rails). You'll find all the documentation in this repository, though.
+
+
+## Installation
+
+System wide:
+
+```console
+gem install yell
+```
+
+Or in your Gemfile:
+
+```ruby
+gem "yell"
+```
+
+
+## Usage
+
+On the basics, you can use Yell just like any other logging library with a more
+sophisticated message formatter.
+
+```ruby
+logger = Yell.new STDOUT
+
+logger.info "Hello World"
+#=> "2012-02-29T09:30:00+01:00 [ INFO] 65784 : Hello World"
+# ^ ^ ^ ^
+# ISO8601 Timestamp Level Pid Message
+```
+
+The strength of Yell, however, comes when using multiple adapters. The already built-in
+ones are IO-based and require no further configuration. Also, there are additional ones
+available as separate gems. Please consult the [wiki](https://github.com/rudionrails/yell/wiki)
+on that - they are listed there.
+
+The standard adapters are:
+
+`:stdout` : Messages will be written to STDOUT
+`:stderr` : Messages will be written to STDERR
+`:file` : Messages will be written to a file
+`:datefile` : Messages will be written to a timestamped file
+
+
+Here are some short examples on how to combine them:
+
+##### Example: Notice messages go into `STDOUT` and error messages into `STDERR`
+
+```ruby
+logger = Yell.new do |l|
+ l.adapter STDOUT, level: [:debug, :info, :warn]
+ l.adapter STDERR, level: [:error, :fatal]
+end
+```
+
+##### Example: Typical production Logger
+
+We setup a logger that starts passing messages at the `:info` level. Severities
+below `:error` go into the 'production.log', whereas anything higher is written
+into the 'error.log'.
+
+```ruby
+logger = Yell.new do |l|
+ l.level = 'gte.info' # will only pass :info and above to the adapters
+
+ l.adapter :datefile, 'production.log', level: 'lte.warn' # anything lower or equal to :warn
+ l.adapter :datefile, 'error.log', level: 'gte.error' # anything greater or equal to :error
+end
+```
+
+##### Example: Typical production Logger for Heroku
+
+When deploying to Heroku, the "rails_log_stdout" gem gets injected to your Rails project.
+Yell does not need that when properly configured (see [yell-rails](https://github.com/rudionrails/yell-rails)
+for a more convenient integration with Rails).
+
+```ruby
+logger = Yell.new do |l|
+ l.level = 'gte.info'
+
+ l.adapter :stdout, level: 'lte.warn'
+ l.adapter :stderr, level: 'gte.error'
+end
+```
+
+### But I'm used to Log4r and I don't want to move on
+
+One of the really nice features of Log4r is its repository. The following example is
+taken from the official Log4r [documentation](http://log4r.rubyforge.org/manual.html#outofbox).
+
+```ruby
+require 'log4r'
+include Log4r
+
+# create a logger named 'mylog' that logs to stdout
+mylog = Logger.new 'mylog'
+mylog.outputters = Outputter.stdout
+
+# later in the code, you can get the logger back
+Logger['mylog']
+```
+
+With Yell you can do the same thing with less:
+
+```ruby
+require 'yell'
+
+# create a logger named 'mylog' that logs to stdout
+Yell.new :stdout, name: 'mylog'
+
+# later in the code, you can get the logger back
+Yell['mylog']
+```
+
+There is no need to define outputters separately and you don't have to taint
+you global namespace with Yell's subclasses.
+
+### Adding a logger to an existing class
+
+Yell comes with a simple module: +Yell::Loggable+. Simply include this in a class and
+you are good to go.
+
+```ruby
+# Before you can use it, you will need to define a logger and
+# provide it with the `:name` of your class.
+Yell.new :stdout, name: 'Foo'
+
+class Foo
+ include Yell::Loggable
+end
+
+# Now you can log
+Foo.logger.info "Hello World"
+Foo.new.logger.info "Hello World"
+```
+
+It even works with class inheritance:
+
+```ruby
+# Given the above example, we inherit from Foo
+class Bar < Foo
+end
+
+# The logger will fallback to the Foo superclass
+Bar.logger.info "Hello World"
+Bar.new.logger.info "Hello World"
+```
+
+### Adding a logger to all classes at once (global logger)
+
+Derived from the example above, simply do the following.
+
+```ruby
+# Define a logger and pass `Object` as name. Internally, Yell adds this
+# logger to the repository where you can access it later on.
+Yell.new :stdout, name: Object
+
+# Enable logging for the class that (almost) every Ruby class inherits from
+Object.send :include, Yell::Loggable
+
+# now you are good to go... from wherever you are
+logger.info "Hello from anything"
+Integer.logger.info "Hello from Integer"
+```
+
+### Suppress log messages with silencers
+
+In case you woul like to suppress certain log messages, you may define
+silencers with Yell. Use this to get control of a noisy log environment. For
+instance, you can suppress logging messages that contain secure information or
+more simply, to skip information about serving your Rails assets. Provide a
+string or a regular expression of the message patterns you would like to exclude.
+
+```ruby
+logger = Yell.new do |l|
+ l.silence /^Started GET "\/assets/
+ l.silence /^Served asset/
+end
+
+logger.debug 'Started GET "/assets/logo.png" for 127.0.0.1 at 2013-06-20 10:18:38 +0200'
+logger.debug 'Served asset /logo.png - 304 Not Modified (0ms)'
+```
+
+### Alter log messages with modifiers
+
+
+## Further Readings
+
+[How To: Setting The Log Level](https://github.com/rudionrails/yell/wiki/101-setting-the-log-level)
+[How To: Formatting Log Messages](https://github.com/rudionrails/yell/wiki/101-formatting-log-messages)
+[How To: Using Adapters](https://github.com/rudionrails/yell/wiki/101-using-adapters)
+[How To: The Datefile Adapter](https://github.com/rudionrails/yell/wiki/101-the-datefile-adapter)
+[How To: Different Adapters for Different Log Levels](https://github.com/rudionrails/yell/wiki/101-different-adapters-for-different-log-levels)
+
+
+### Additional Adapters
+[Syslog](https://github.com/rudionrails/yell/wiki/additional-adapters-syslog)
+[Graylog2 (GELF)](https://github.com/rudionrails/yell/wiki/additional-adapters-gelf)
+[Fluentd](https://github.com/red5studios/yell-adapters-fluentd)
+
+
+### Development
+
+[How To: Writing Your Own Adapter](https://github.com/rudionrails/yell/wiki/Writing-your-own-adapter)
+
+You can find further examples and additional adapters in the [wiki](https://github.com/rudionrails/yell/wiki).
+or have a look into the examples folder.
+
+
+Copyright © 2011-2014 Rudolf Schmidt, released under the MIT license
+
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 0000000..d31c466
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,14 @@
+require 'bundler/gem_tasks'
+
+# Run stuff in the examples folder
+desc "Run examples"
+task :examples do
+ require 'benchmark'
+
+ seconds = Benchmark.realtime do
+ Dir[ './examples/*.rb' ].each { |file| puts "\n\n=== Running #{file} ==="; require file }
+ end
+
+ puts "\n\t[ Examples took #{seconds} seconds to run ]"
+end
+
diff --git a/examples/001-basic-usage.rb b/examples/001-basic-usage.rb
new file mode 100644
index 0000000..a982c28
--- /dev/null
+++ b/examples/001-basic-usage.rb
@@ -0,0 +1,25 @@
+# encoding: utf-8
+
+require_relative '../lib/yell'
+
+puts <<-EOS
+
+# On the basics, Yell works just like any other logging library.
+#
+# However, it enriches your log messages to make it more readable. By default,
+# it will format the given message as follows:
+
+logger = Yell.new STDOUT
+logger.info "Hello World!"
+
+#=> "2012-02-29T09:30:00+01:00 [ INFO] 65784 : Hello World!"
+# ^ ^ ^ ^
+# ISO8601 Timestamp Level Pid Message
+
+
+EOS
+
+puts "=== actual example ==="
+logger = Yell.new STDOUT
+logger.info "Hello World!"
+
diff --git a/examples/002.1-log-level-basics.rb b/examples/002.1-log-level-basics.rb
new file mode 100644
index 0000000..e3da483
--- /dev/null
+++ b/examples/002.1-log-level-basics.rb
@@ -0,0 +1,25 @@
+# encoding: utf-8
+
+require_relative '../lib/yell'
+
+puts <<-EOS
+
+# Like many other logging libraries, Yell allows you to define from which level
+# onwards you want to write your log message.
+
+logger = Yell.new STDOUT, :level => :info
+
+logger.debug "This is a :debug message"
+#=> nil
+
+logger.info "This is a :info message"
+#=> "2012-02-29T09:30:00+01:00 [ INFO] 65784 : This is a :info message"
+
+
+EOS
+
+puts "=== actual example ==="
+logger = Yell.new STDOUT, :level => :info
+
+logger.debug "This is a :debug message"
+logger.info "This is a :info message"
diff --git a/examples/002.2-log-level-on-certain-severities-only.rb b/examples/002.2-log-level-on-certain-severities-only.rb
new file mode 100644
index 0000000..05c9536
--- /dev/null
+++ b/examples/002.2-log-level-on-certain-severities-only.rb
@@ -0,0 +1,28 @@
+# encoding: utf-8
+
+require_relative '../lib/yell'
+
+puts <<-EOS
+
+# The Yell::Level parser allows you to exactly specify on which levels to log,
+# ignoring all the others. For instance: If we want to only log at the :debug
+# and :warn levels we simply providing an array:
+
+logger = Yell.new STDOUT, :level => [:debug, :warn]
+
+[:debug, :info, :warn, :error, :fatal].each do |level|
+ logger.send( level, level )
+end
+#=> "2012-02-29T09:30:00+01:00 [DEBUG] 65784 : debug"
+#=> "2012-02-29T09:30:00+01:00 [ WARN] 65784 : warn"
+
+
+EOS
+
+puts "=== actual example ==="
+logger = Yell.new STDOUT, :level => [:debug, :warn]
+
+[:debug, :info, :warn, :error, :fatal].each do |level|
+ logger.send( level, level )
+end
+
diff --git a/examples/002.3-log-level-within-range.rb b/examples/002.3-log-level-within-range.rb
new file mode 100644
index 0000000..81fa555
--- /dev/null
+++ b/examples/002.3-log-level-within-range.rb
@@ -0,0 +1,28 @@
+# encoding: utf-8
+
+require_relative '../lib/yell'
+
+puts <<-EOS
+
+# Additionally to writing only on specific levels, you may pass a range to
+# the :level option:
+
+logger = Yell.new STDOUT, :level => (:debug..:warn)
+
+[:debug, :info, :warn, :error, :fatal].each do |level|
+ logger.send( level, level )
+end
+#=> "2012-02-29T09:30:00+01:00 [DEBUG] 65784 : debug"
+#=> "2012-02-29T09:30:00+01:00 [ INFO] 65784 : info"
+#=> "2012-02-29T09:30:00+01:00 [ WARN] 65784 : warn"
+
+
+EOS
+
+puts "=== actuale example ==="
+logger = Yell.new STDOUT, :level => (:debug..:warn)
+
+[:debug, :info, :warn, :error, :fatal].each do |level|
+ logger.send( level, level )
+end
+
diff --git a/examples/003.1-formatting-DefaultFormat.rb b/examples/003.1-formatting-DefaultFormat.rb
new file mode 100644
index 0000000..b597845
--- /dev/null
+++ b/examples/003.1-formatting-DefaultFormat.rb
@@ -0,0 +1,22 @@
+# encoding: utf-8
+
+require_relative '../lib/yell'
+
+puts <<-EOS
+
+# The default formatting string looks like: %d [%5L] %p : %m and is used when
+# nothing else is defined.
+
+logger = Yell.new STDOUT, :format => Yell::DefaultFormat
+logger.info "Hello World!"
+#=> "2012-02-29T09:30:00+01:00 [ INFO] 65784 : Hello World!"
+# ^ ^ ^ ^
+# ISO8601 Timestamp Level Pid Message
+
+
+EOS
+
+puts "=== actuale example ==="
+logger = Yell.new STDOUT, :format => Yell::DefaultFormat
+logger.info "Hello World!"
+
diff --git a/examples/003.2-formatting-BasicFormat.rb b/examples/003.2-formatting-BasicFormat.rb
new file mode 100644
index 0000000..8d1e159
--- /dev/null
+++ b/examples/003.2-formatting-BasicFormat.rb
@@ -0,0 +1,22 @@
+# encoding: utf-8
+
+require_relative '../lib/yell'
+
+puts <<-EOS
+
+# The basic formating string looks like: %l, %d: %m.
+
+logger = Yell.new STDOUT, :format => Yell::BasicFormat
+logger.info "Hello World!"
+#=> "I, 2012-02-29T09:30:00+01:00 : Hello World!"
+# ^ ^ ^
+# ^ ISO8601 Timestamp Message
+# Level (short)
+
+
+EOS
+
+puts "=== actuale example ==="
+logger = Yell.new STDOUT, :format => Yell::BasicFormat
+logger.info "Hello World!"
+
diff --git a/examples/003.3-formatting-ExtendedFormat.rb b/examples/003.3-formatting-ExtendedFormat.rb
new file mode 100644
index 0000000..5bc919b
--- /dev/null
+++ b/examples/003.3-formatting-ExtendedFormat.rb
@@ -0,0 +1,21 @@
+# encoding: utf-8
+
+require_relative '../lib/yell'
+
+puts <<-EOS
+
+# The extended formatting string looks like: %d [%5L] %p %h : %m.
+
+logger = Yell.new STDOUT, :format => Yell::ExtendedFormat
+logger.info "Hello World!"
+#=> "2012-02-29T09:30:00+01:00 [ INFO] 65784 localhost : Hello World!"
+# ^ ^ ^ ^ ^
+# ISO8601 Timestamp Level Pid Hostname Message
+
+
+EOS
+
+puts "=== actuale example ==="
+logger = Yell.new STDOUT, :format => Yell::ExtendedFormat
+logger.info "Hello World!"
+
diff --git a/examples/003.4-formatting-on-your-own.rb b/examples/003.4-formatting-on-your-own.rb
new file mode 100644
index 0000000..33097c1
--- /dev/null
+++ b/examples/003.4-formatting-on-your-own.rb
@@ -0,0 +1,21 @@
+# encoding: utf-8
+
+require_relative '../lib/yell'
+
+puts <<-EOS
+
+# The extended formatting string looks like: %d [%5L] %p %h : %m.
+
+logger = Yell.new STDOUT, :format => "[%f:%n in `%M'] %m", :trace => true
+logger.info "Hello World!"
+#=> [003.4-formatting-on-your-own.rb:20 in `<main>'] Hello World!
+# ^ ^ ^ ^
+# filename line method message
+
+
+EOS
+
+puts "=== actuale example ==="
+logger = Yell.new STDOUT, :format => "[%f:%n in `%M'] %m", :trace => true
+logger.info "Hello World!"
+
diff --git a/examples/004.1-colorizing-the-log-output.rb b/examples/004.1-colorizing-the-log-output.rb
new file mode 100644
index 0000000..13e784e
--- /dev/null
+++ b/examples/004.1-colorizing-the-log-output.rb
@@ -0,0 +1,23 @@
+# encoding: utf-8
+
+require_relative '../lib/yell'
+
+puts <<-EOS
+
+You may colorize the log output on your io-based loggers loke so:
+
+logger = Yell.new STDOUT, :colors => true
+
+Yell::Severities.each do |level|
+ logger.send level.downcase, level
+end
+
+EOS
+
+puts "=== actuale example ==="
+logger = Yell.new STDOUT, :colors => true
+
+Yell::Severities.each do |level|
+ logger.send level.downcase, level
+end
+
diff --git a/examples/005.1-repository.rb b/examples/005.1-repository.rb
new file mode 100644
index 0000000..f17715a
--- /dev/null
+++ b/examples/005.1-repository.rb
@@ -0,0 +1,21 @@
+# encoding: utf-8
+
+require_relative '../lib/yell'
+
+puts <<-EOS
+
+# You can add a logger to the global repository.
+#
+# create a logger named 'mylog' that logs to stdout
+Yell.new :stdout, :name => 'mylog'
+
+# Later in the code, you can get your logger back
+Yell['mylog'].info "Hello World!"
+
+
+EOS
+
+puts "=== actuale example ==="
+Yell.new :stdout, :name => 'mylog'
+Yell['mylog'].info "Hello World!"
+
diff --git a/examples/006.1-the-loggable-module.rb b/examples/006.1-the-loggable-module.rb
new file mode 100644
index 0000000..7adc0b8
--- /dev/null
+++ b/examples/006.1-the-loggable-module.rb
@@ -0,0 +1,37 @@
+# encoding: utf-8
+
+require_relative '../lib/yell'
+
+puts <<-EOS
+
+# You can add logging to any class by including the Yell::Loggable module.
+#
+# When including the module, your class will get a :logger method. Before you
+# can use it, though, you will need to define a logger providing the :name of
+# your class.
+
+Yell.new :stdout, :name => 'Foo'
+
+# Define the class
+class Foo
+ include Yell::Loggable
+end
+
+foo = Foo.new
+foo.logger.info "Hello World!"
+#=> "2012-02-29T09:30:00+01:00 [ INFO] 65784 : Hello World!"
+
+
+EOS
+
+puts "=== actual example ==="
+
+Yell.new :stdout, :name => 'Foo'
+
+class Foo
+ include Yell::Loggable
+end
+
+foo = Foo.new
+foo.logger.info "Hello World!"
+
diff --git a/examples/006.2-the-loggable-module-with-inheritance.rb b/examples/006.2-the-loggable-module-with-inheritance.rb
new file mode 100644
index 0000000..0c899c9
--- /dev/null
+++ b/examples/006.2-the-loggable-module-with-inheritance.rb
@@ -0,0 +1,41 @@
+# encoding: utf-8
+
+require_relative '../lib/yell'
+
+puts <<-EOS
+
+# You can add logging to any class by including the Yell::Loggable module.
+#
+# When including the module, your class will get a :logger method. Before you
+# can use it, though, you will need to define a logger providing the :name of
+# your class.
+
+Yell.new :stdout, :name => 'Foo'
+
+# Define the class
+class Foo
+ include Yell::Loggable
+end
+
+class Bar < Foo; end
+
+bar = Bar.new
+bar.logger.info "Hello World!"
+#=> "2012-02-29T09:30:00+01:00 [ INFO] 65784 : Hello World!"
+
+
+EOS
+
+puts "=== actual example ==="
+
+Yell.new :stdout, :name => 'Foo'
+
+class Foo
+ include Yell::Loggable
+end
+
+class Bar < Foo; end
+
+bar = Bar.new
+bar.logger.info "Hello World!"
+
diff --git a/lib/core_ext/logger.rb b/lib/core_ext/logger.rb
new file mode 100644
index 0000000..3e1ddba
--- /dev/null
+++ b/lib/core_ext/logger.rb
@@ -0,0 +1,18 @@
+require 'logger'
+
+class Logger
+
+ def level_with_yell=( level )
+ self.level_without_yell= Integer(level)
+ end
+ alias_method :level_without_yell=, :level=
+ alias_method :level=, :level_with_yell=
+
+ def add_with_yell( severity, message = nil, progname = nil, &block )
+ add_without_yell(Integer(severity), message, progname, &block)
+ end
+ alias_method :add_without_yell, :add
+ alias_method :add, :add_with_yell
+
+end
+
diff --git a/lib/yell.rb b/lib/yell.rb
new file mode 100644
index 0000000..5c681e7
--- /dev/null
+++ b/lib/yell.rb
@@ -0,0 +1,151 @@
+# encoding: utf-8
+
+# Copyright (c) 2011-2014 Rudolf Schmidt
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+module Yell #:nodoc:
+
+ # Holds all Yell severities
+ Severities = ['DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL', 'UNKNOWN'].freeze
+
+ class << self
+ # Creates a new logger instance.
+ #
+ # Refer to #Yell::Loggger for usage.
+ #
+ # @return [Yell::Logger] The logger instance
+ def new( *args, &block )
+ Yell::Logger.new(*args, &block)
+ end
+
+ # Shortcut to Yell::Level.new
+ #
+ # @return [Yell::Level] The level instance
+ def level( val = nil )
+ Yell::Level.new(val)
+ end
+
+ # Shortcut to Yell::Repository[]
+ #
+ # @return [Yell::Logger] The logger instance
+ def []( name )
+ Yell::Repository[name]
+ end
+
+ # Shortcut to Yell::Repository[]=
+ #
+ # @return [Yell::Logger] The logger instance
+ def []=( name, logger )
+ Yell::Repository[name] = logger
+ end
+
+ # Shortcut to Yell::Fomatter.new
+ #
+ # @return [Yell::Formatter] A Yell::Formatter instance
+ def format( pattern = nil, date_pattern = nil, &block )
+ Yell::Formatter.new(pattern, date_pattern, &block)
+ end
+
+ # Loads a config from a YAML file
+ #
+ # @return [Yell::Logger] The logger instance
+ def load!( file )
+ Yell.new Yell::Configuration.load!(file)
+ end
+
+ # Shortcut to Yell::Adapters.register
+ def register( name, klass )
+ Yell::Adapters.register(name, klass)
+ end
+
+ # @private
+ def env
+ return ENV['YELL_ENV'] if ENV.key? 'YELL_ENV'
+ return ENV['RACK_ENV'] if ENV.key? 'RACK_ENV'
+ return ENV['RAILS_ENV'] if ENV.key? 'RAILS_ENV'
+
+ if defined?(Rails)
+ Rails.env
+ else
+ 'development'
+ end
+ end
+
+ # @private
+ def __deprecate__( version, message, options = {} ) #:nodoc:
+ messages = ["Deprecation Warning (since v#{version}): #{message}" ]
+ messages << " before: #{options[:before]}" if options[:before]
+ messages << " after: #{options[:after]}" if options[:after]
+
+ __warn__(*messages)
+ end
+
+ # @private
+ def __warn__( *messages ) #:nodoc:
+ $stderr.puts "[Yell] " + messages.join("\n")
+ rescue Exception => e
+ # do nothing
+ end
+
+ # @private
+ def __fetch__( hash, *args )
+ options = args.last.is_a?(Hash) ? args.pop : {}
+ value = args.map { |key| hash.fetch(key.to_sym, hash[key.to_s]) }.compact.first
+
+ value.nil? ? options[:default] : value
+ end
+
+ end
+
+end
+
+# helpers
+require File.dirname(__FILE__) + '/yell/helpers/base'
+require File.dirname(__FILE__) + '/yell/helpers/adapter'
+require File.dirname(__FILE__) + '/yell/helpers/formatter'
+require File.dirname(__FILE__) + '/yell/helpers/level'
+require File.dirname(__FILE__) + '/yell/helpers/tracer'
+require File.dirname(__FILE__) + '/yell/helpers/silencer'
+
+# classes
+require File.dirname(__FILE__) + '/yell/configuration'
+require File.dirname(__FILE__) + '/yell/repository'
+require File.dirname(__FILE__) + '/yell/event'
+require File.dirname(__FILE__) + '/yell/level'
+require File.dirname(__FILE__) + '/yell/formatter'
+require File.dirname(__FILE__) + '/yell/silencer'
+require File.dirname(__FILE__) + '/yell/adapters'
+require File.dirname(__FILE__) + '/yell/logger'
+
+# modules
+require File.dirname(__FILE__) + '/yell/loggable'
+
+# core extensions
+require File.dirname(__FILE__) + '/core_ext/logger'
+
+# register known adapters
+Yell.register :null, Yell::Adapters::Base # adapter that does nothing (for convenience only)
+Yell.register :file, Yell::Adapters::File
+Yell.register :datefile, Yell::Adapters::Datefile
+Yell.register :stdout, Yell::Adapters::Stdout
+Yell.register :stderr, Yell::Adapters::Stderr
+
+
diff --git a/lib/yell/adapters.rb b/lib/yell/adapters.rb
new file mode 100644
index 0000000..4de0260
--- /dev/null
+++ b/lib/yell/adapters.rb
@@ -0,0 +1,88 @@
+# encoding: utf-8
+module Yell #:nodoc:
+
+ # AdapterNotFound is raised whenever you want to instantiate an
+ # adapter that does not exist.
+ class AdapterNotFound < StandardError; end
+
+ # This module provides the interface to attaching adapters to
+ # the logger. You should not have to call the corresponding classes
+ # directly.
+ module Adapters
+
+ class Collection
+ def initialize( options = {} )
+ @options = options
+ @collection = []
+ end
+
+ def add( type = :file, *args, &block )
+ options = [@options, *args].inject(Hash.new) do |h, c|
+ h.merge( [String, Pathname].include?(c.class) ? {:filename => c} : c )
+ end
+
+ # remove possible :null adapters
+ @collection.shift if @collection.first.instance_of?(Yell::Adapters::Base)
+
+ new_adapter = Yell::Adapters.new(type, options, &block)
+ @collection.push(new_adapter)
+
+ new_adapter
+ end
+
+ def empty?
+ @collection.empty?
+ end
+
+ # @private
+ def write( event )
+ @collection.each { |c| c.write(event) }
+ true
+ end
+
+ # @private
+ def close
+ @collection.each { |c| c.close }
+ end
+ end
+
+ # holds the list of known adapters
+ @adapters = {}
+
+ # Register your own adapter here
+ #
+ # @example
+ # Yell::Adapters.register( :myadapter, MyAdapter )
+ def self.register( name, klass )
+ @adapters[name.to_sym] = klass
+ end
+
+ # Returns an instance of the given processor type.
+ #
+ # @example A simple file adapter
+ # Yell::Adapters.new( :file )
+ def self.new( name, options = {}, &block )
+ return name if name.is_a?(Yell::Adapters::Base)
+
+ adapter = case name
+ when STDOUT then @adapters[:stdout]
+ when STDERR then @adapters[:stderr]
+ else @adapters[name.to_sym]
+ end
+
+ raise AdapterNotFound.new(name) if adapter.nil?
+ adapter.new(options, &block)
+ end
+
+ end
+end
+
+# Base for all adapters
+require File.dirname(__FILE__) + '/adapters/base'
+
+# IO based adapters
+require File.dirname(__FILE__) + '/adapters/io'
+require File.dirname(__FILE__) + '/adapters/streams'
+require File.dirname(__FILE__) + '/adapters/file'
+require File.dirname(__FILE__) + '/adapters/datefile'
+
diff --git a/lib/yell/adapters/base.rb b/lib/yell/adapters/base.rb
new file mode 100644
index 0000000..cd2ed35
--- /dev/null
+++ b/lib/yell/adapters/base.rb
@@ -0,0 +1,231 @@
+# encoding: utf-8
+
+require 'monitor'
+
+module Yell #:nodoc:
+ module Adapters #:nodoc:
+
+ # This class provides the basic interface for all allowed operations on any
+ # adapter implementation. Other adapters should inherit from it for the methods
+ # used by the {Yell::Logger}.
+ #
+ # Writing your own adapter is really simple. Inherit from the base class and use
+ # the `setup`, `write` and `close` methods. Yell requires the `write` method to be
+ # specified (`setup` and `close` are optional).
+ #
+ #
+ # The following example shows how to define a basic Adapter to format and print
+ # log events to STDOUT:
+ #
+ # class PutsAdapter < Yell::Adapters::Base
+ # include Yell::Formatter::Helpers
+ #
+ # setup do |options|
+ # self.format = options[:format]
+ # end
+ #
+ # write do |event|
+ # message = format.call(event)
+ #
+ # STDOUT.puts message
+ # end
+ # end
+ #
+ #
+ # After the Adapter has been written, we need to register it to Yell:
+ #
+ # Yell::Adapters.register :puts, PutsAdapter
+ #
+ # Now, we can use it like so:
+ #
+ # logger = Yell.new :puts
+ # logger.info "Hello World!"
+ class Base < Monitor
+ include Yell::Helpers::Base
+ include Yell::Helpers::Level
+
+ class << self
+ # Setup your adapter with this helper method.
+ #
+ # @example
+ # setup do |options|
+ # @file_handle = File.new( '/dev/null', 'w' )
+ # end
+ def setup( &block )
+ compile!(:setup!, &block)
+ end
+
+ # Define your write method with this helper.
+ #
+ # @example Printing messages to file
+ # write do |event|
+ # @file_handle.puts event.message
+ # end
+ def write( &block )
+ compile!(:write!, &block)
+ end
+
+ # Define your open method with this helper.
+ #
+ # @example Open a file handle
+ # open do
+ # @stream = ::File.open( 'test.log', ::File::WRONLY|::File::APPEND|::File::CREAT )
+ # end
+ def open( &block )
+ compile!(:open!, &block)
+ end
+
+ # Define your close method with this helper.
+ #
+ # @example Closing a file handle
+ # close do
+ # @stream.close
+ # end
+ def close( &block )
+ compile!(:close!, &block)
+ end
+
+
+ private
+
+ # Pretty funky code block, I know but here is what it basically does:
+ #
+ # @example
+ # compile! :write! do |event|
+ # puts event.message
+ # end
+ #
+ # # Is actually defining the `:write!` instance method with a call to super:
+ #
+ # def write!( event )
+ # puts event.method
+ # super
+ # end
+ def compile!( name, &block )
+ # Get the already defined method
+ m = instance_method( name )
+
+ # Create a new method with leading underscore
+ define_method("_#{name}", &block)
+ _m = instance_method("_#{name}")
+ remove_method("_#{name}")
+
+ # Define instance method
+ define!(name, _m, m, &block)
+ end
+
+ # Define instance method by given name and call the unbound
+ # methods in order with provided block.
+ def define!( name, _m, m, &block )
+ if block.arity == 0
+ define_method(name) do
+ _m.bind(self).call
+ m.bind(self).call
+ end
+ else
+ define_method(name) do |*args|
+ _m.bind(self).call(*args)
+ m.bind(self).call(*args)
+ end
+ end
+ end
+ end
+
+
+ # Initializes a new Adapter.
+ #
+ # You should not overload the constructor, use #setup instead.
+ def initialize( options = {}, &block )
+ super() # init the monitor superclass
+
+ reset!
+ setup!(options)
+
+ # eval the given block
+ block.arity > 0 ? block.call(self) : instance_eval(&block) if block_given?
+ end
+
+ # The main method for calling the adapter.
+ #
+ # The method receives the log `event` and determines whether to
+ # actually write or not.
+ def write( event )
+ synchronize { write!(event) } if write?(event)
+ rescue Exception => e
+ # make sure the adapter is closed and re-raise the exception
+ synchronize { close }
+
+ raise(e)
+ end
+
+ # Close the adapter (stream, connection, etc).
+ #
+ # Adapter classes should provide their own implementation
+ # of this method.
+ def close
+ close!
+ end
+
+ # Get a pretty string representation of the adapter, including
+ def inspect
+ inspection = inspectables.map { |m| "#{m}: #{send(m).inspect}" }
+ "#<#{self.class.name} #{inspection * ', '}>"
+ end
+
+
+ private
+
+ # Setup the adapter instance.
+ #
+ # Adapter classes should provide their own implementation
+ # of this method (if applicable).
+ def setup!( options )
+ self.level = Yell.__fetch__(options, :level)
+ end
+
+ # Perform the actual write.
+ #
+ # Adapter classes must provide their own implementation
+ # of this method.
+ def write!( event )
+ # Not implemented
+ end
+
+ # Perform the actual open.
+ #
+ # Adapter classes should provide their own implementation
+ # of this method.
+ def open!
+ # Not implemented
+ end
+
+ # Perform the actual close.
+ #
+ # Adapter classes should provide their own implementation
+ # of this method.
+ def close!
+ # Not implemented
+ end
+
+ # Determine whether to write at the given severity.
+ #
+ # @example
+ # write? Yell::Event.new( 'INFO', 'Hwllo Wold!' )
+ #
+ # @param [Yell::Event] event The log event
+ #
+ # @return [Boolean] true or false
+ def write?( event )
+ level.nil? || level.at?(event.level)
+ end
+
+ # Get an array of inspected attributes for the adapter.
+ def inspectables
+ [:level]
+ end
+
+ end
+
+ end
+end
+
diff --git a/lib/yell/adapters/datefile.rb b/lib/yell/adapters/datefile.rb
new file mode 100644
index 0000000..7b42be6
--- /dev/null
+++ b/lib/yell/adapters/datefile.rb
@@ -0,0 +1,194 @@
+# encoding: utf-8
+
+module Yell #:nodoc:
+ module Adapters #:nodoc:
+
+ # The +Datefile+ adapter is similar to the +File+ adapter. However, it
+ # rotates the file at midnight (by default).
+ class Datefile < Yell::Adapters::File
+
+ # The default date pattern, e.g. "19820114" (14 Jan 1982)
+ DefaultDatePattern = "%Y%m%d"
+
+ # Metadata
+ Header = lambda { |date, pattern| "# -*- #{date.iso8601} (#{date.to_f}) [#{pattern}] -*-" }
+ HeaderRegexp = /^# -\*- (.+) \((\d+\.\d+)\) \[(.+)\] -\*-$/
+
+ # The pattern to be used for the files
+ #
+ # @example
+ # date_pattern = "%Y%m%d" # default
+ # date_pattern = "%Y-week-%V"
+ attr_accessor :date_pattern
+
+ # Tell the adapter to create a symlink onto the currently
+ # active (timestamped) file. Upon rollover, the symlink is
+ # set to the newly created file, and so on.
+ #
+ # @example
+ # symlink = true
+ attr_accessor :symlink
+
+ # Set the amount of logfiles to keep when rolling over.
+ # By default, no files will be cleaned up.
+ #
+ # @example Keep the last 5 logfiles
+ # keep = 5
+ # keep = '10'
+ #
+ # @example Do not clean up any files
+ # keep = 0
+ attr_accessor :keep
+
+ # You can suppress the first line of the logfile that contains
+ # the metadata. This is important upon rollover, because on *nix
+ # systems, it is not possible to determine the creation time of a file,
+ # on the last access time. The header compensates this.
+ #
+ # @example
+ # header = false
+ attr_accessor :header
+
+
+ private
+
+ # @overload setup!( options )
+ def setup!( options )
+ self.header = Yell.__fetch__(options, :header, :default => true)
+ self.date_pattern = Yell.__fetch__(options, :date_pattern, :default => DefaultDatePattern)
+ self.keep = Yell.__fetch__(options, :keep, :default => false)
+ self.symlink = Yell.__fetch__(options, :symlink, :default => true)
+
+ @original_filename = ::File.expand_path(Yell.__fetch__(options, :filename, :default => default_filename))
+ options[:filename] = @original_filename
+
+ @date = Time.now
+ @date_strftime = @date.strftime(date_pattern)
+
+ super
+ end
+
+ # @overload write!( event )
+ def write!( event )
+ # do nothing when not closing
+ return super unless close?
+ close
+
+ # exit when file ready present
+ return super if ::File.exist?(@filename)
+
+ header! if header?
+ symlink! if symlink?
+ cleanup! if cleanup?
+
+ super
+ end
+
+ # @overload close!
+ def close!
+ @filename = filename_for(@date)
+
+ super
+ end
+
+ # Determine whether to close the file handle or not.
+ #
+ # It is based on the `:date_pattern` (can be passed as option upon initialize).
+ # If the current time hits the pattern, it closes the file stream.
+ #
+ # @return [Boolean] true or false
+ def close?
+ _date = Time.now
+ _date_strftime = _date.strftime(date_pattern)
+
+ if @stream.nil? or _date_strftime != @date_strftime
+ @date, @date_strftime = _date, _date_strftime
+
+ return true
+ end
+
+ false
+ end
+
+ # Removes old logfiles of the same date pattern.
+ #
+ # By reading the header of the files that match the date pattern, the
+ # adapter determines whether to remove them or not. If no header is present,
+ # it makes the best guess by checking the last access time (which may result
+ # in false cleanups).
+ def cleanup!
+ files = Dir[ @original_filename.sub(/(\.\w+)?$/, ".*\\1") ].sort.select do |file|
+ _, pattern = header_from(file)
+
+ # Select if the date pattern is nil (no header info available within the file) or
+ # when the pattern matches.
+ pattern.nil? || pattern == self.date_pattern
+ end
+
+ ::File.unlink( *files[0..-keep-1] )
+ end
+
+ # Cleanup old logfiles?
+ #
+ # @return [Boolean] true or false
+ def cleanup?
+ !!keep && keep.to_i > 0
+ end
+
+ # Symlink the current filename to the original one.
+ def symlink!
+ # do nothing, because symlink is already correct
+ return if ::File.symlink?(@original_filename) && ::File.readlink(@original_filename) == @filename
+
+ ::File.unlink(@original_filename) if ::File.exist?(@original_filename) || ::File.symlink?(@original_filename)
+ ::File.symlink(@filename, @original_filename)
+ end
+
+ # Symlink the original filename?
+ #
+ # @return [Boolean] true or false
+ def symlink?
+ !!symlink
+ end
+
+ # Write the header information into the file
+ def header!
+ stream.puts( Header.call(@date, date_pattern) )
+ end
+
+ # Write header into the file?
+ #
+ # @return [Boolean] true or false
+ def header?
+ !!header
+ end
+
+ # Sets the filename with the `:date_pattern` appended to it.
+ def filename_for( date )
+ @original_filename.sub(/(\.\w+)?$/, ".#{date.strftime(date_pattern)}\\1")
+ end
+
+ # Fetch the header form the file
+ def header_from( file )
+ if m = ::File.open(file, &:readline).match(HeaderRegexp)
+ # in case there is a Header present, we can just read from it
+ [ Time.at(m[2].to_f), m[3] ]
+ else
+ # In case there is no header: we need to take a good guess
+ #
+ # Since the pattern can not be determined, we will just return the Posix ctime.
+ # That is NOT the creatint time, so the value will potentially be wrong!
+ [::File.ctime(file), nil]
+ end
+ end
+
+ # @overload inspectables
+ def inspectables
+ super.concat [:date_pattern, :header, :keep, :symlink ]
+ end
+
+ end
+
+ end
+end
+
diff --git a/lib/yell/adapters/file.rb b/lib/yell/adapters/file.rb
new file mode 100644
index 0000000..76fd2e7
--- /dev/null
+++ b/lib/yell/adapters/file.rb
@@ -0,0 +1,36 @@
+# encoding: utf-8
+
+module Yell #:nodoc:
+ module Adapters #:nodoc:
+
+ # The +File+ adapter is the most basic. As one would expect, it's used
+ # for logging into files.
+ class File < Yell::Adapters::Io
+
+ private
+
+ # @overload setup!( options )
+ def setup!( options )
+ @filename = ::File.expand_path(Yell.__fetch__(options, :filename, :default => default_filename))
+
+ super
+ end
+
+ # @overload open!
+ def open!
+ @stream = ::File.open(@filename, ::File::WRONLY|::File::APPEND|::File::CREAT)
+
+ super
+ end
+
+ def default_filename #:nodoc:
+ logdir = ::File.expand_path("log")
+
+ ::File.expand_path(::File.directory?(logdir) ? "#{logdir}/#{Yell.env}.log" : "#{Yell.env}.log")
+ end
+
+ end
+
+ end
+end
+
diff --git a/lib/yell/adapters/io.rb b/lib/yell/adapters/io.rb
new file mode 100644
index 0000000..904a873
--- /dev/null
+++ b/lib/yell/adapters/io.rb
@@ -0,0 +1,102 @@
+# encoding: utf-8
+
+module Yell #:nodoc:
+ module Adapters #:nodoc:
+
+ class Io < Yell::Adapters::Base
+ include Yell::Helpers::Formatter
+
+ # The possible unix log colors
+ TTYColors = {
+ 0 => "\033[1;32m", # green
+ 1 => "\033[0m", # normal
+ 2 => "\033[1;33m", # yellow
+ 3 => "\033[1;31m", # red
+ 4 => "\033[1;35m", # magenta
+ 5 => "\033[1;36m", # cyan
+ -1 => "\033[0m" # normal
+ }
+
+ # Sets the “sync mode” to true or false.
+ #
+ # When true (default), every log event is immediately written to the file.
+ # When false, the log event is buffered internally.
+ attr_accessor :sync
+
+ # Sets colored output on or off (default off)
+ #
+ # @example Enable colors
+ # colors = true
+ #
+ # @example Disable colors
+ # colors = false
+ attr_accessor :colors
+
+ # Shortcut to enable colors.
+ #
+ # @example
+ # colorize!
+ def colorize!; @colors = true; end
+
+
+ private
+
+ # @overload setup!( options )
+ def setup!( options )
+ @stream = nil
+
+ self.colors = Yell.__fetch__(options, :colors, :default => false)
+ self.formatter = Yell.__fetch__(options, :format, :formatter)
+ self.sync = Yell.__fetch__(options, :sync, :default => true)
+
+ super
+ end
+
+ # @overload write!( event )
+ def write!( event )
+ message = formatter.call(event)
+
+ # colorize if applicable
+ if colors and color = TTYColors[event.level]
+ message = color + message + TTYColors[-1]
+ end
+
+ stream.syswrite(message)
+
+ super
+ end
+
+ # @overload open!
+ def open!
+ @stream.sync = self.sync if @stream.respond_to?(:sync)
+ @stream.flush if @stream.respond_to?(:flush)
+
+ super
+ end
+
+ # @overload close!
+ def close!
+ @stream.close if @stream.respond_to?(:close)
+ @stream = nil
+
+ super
+ end
+
+ # The IO stream
+ #
+ # Adapter classes should provide their own implementation
+ # of this method.
+ def stream
+ synchronize { open! if @stream.nil?; @stream }
+ end
+
+ # @overload inspectables
+ def inspectables
+ super.concat [:formatter, :colors, :sync]
+ end
+
+ end
+
+ end
+end
+
diff --git a/lib/yell/adapters/streams.rb b/lib/yell/adapters/streams.rb
new file mode 100644
index 0000000..4532f83
--- /dev/null
+++ b/lib/yell/adapters/streams.rb
@@ -0,0 +1,32 @@
+# encoding: utf-8
+
+module Yell #:nodoc:
+ module Adapters #:nodoc:
+
+ class Stdout < Yell::Adapters::Io
+
+ private
+
+ # @overload open!
+ def open!
+ @stream = $stdout.clone
+ super
+ end
+
+ end
+
+ class Stderr < Yell::Adapters::Io
+
+ private
+
+ # @overload open!
+ def open!
+ @stream = $stderr.clone
+ super
+ end
+
+ end
+
+ end
+end
+
diff --git a/lib/yell/configuration.rb b/lib/yell/configuration.rb
new file mode 100644
index 0000000..44a810a
--- /dev/null
+++ b/lib/yell/configuration.rb
@@ -0,0 +1,25 @@
+# encoding: utf-8
+
+require 'erb'
+require 'yaml'
+
+module Yell #:nodoc:
+
+ # The Configuration can be used to setup Yell before
+ # initializing an instance.
+ class Configuration
+
+ def self.load!( file )
+ yaml = YAML.load( ERB.new(File.read(file)).result )
+
+ # in case we have ActiveSupport
+ if defined?(ActiveSupport::HashWithIndifferentAccess)
+ yaml = ActiveSupport::HashWithIndifferentAccess.new(yaml)
+ end
+
+ yaml[Yell.env] || {}
+ end
+
+ end
+end
+
diff --git a/lib/yell/event.rb b/lib/yell/event.rb
new file mode 100644
index 0000000..48c6f26
--- /dev/null
+++ b/lib/yell/event.rb
@@ -0,0 +1,130 @@
+# encoding: utf-8
+
+require 'time'
+require 'socket'
+
+module Yell #:nodoc:
+
+ # Yell::Event.new( :info, 'Hello World', { :scope => 'Application' } )
+ # #=> Hello World scope: Application
+ class Event
+ # regex to fetch caller attributes
+ CallerRegexp = /^(.+?):(\d+)(?::in `(.+)')?/
+
+ # jruby and rubinius seem to have a different caller
+ CallerIndex = defined?(RUBY_ENGINE) && ["rbx", "jruby"].include?(RUBY_ENGINE) ? 1 : 2
+
+
+ class Options
+ attr_reader :severity
+ attr_reader :caller_offset
+
+ def initialize( severity, caller_offset )
+ @severity = severity
+ @caller_offset = caller_offset
+ end
+
+ def <=>( other )
+ @severity <=> other
+ end
+
+ alias :to_i :severity
+ alias :to_int :severity
+ end
+
+ # Prefetch those values (no need to do that on every new instance)
+ @@hostname = Socket.gethostname rescue nil
+ @@progname = $0
+
+ # Accessor to the log level
+ attr_reader :level
+
+ # Accessor to the log message
+ attr_reader :messages
+
+ # Accessor to the time the log event occured
+ attr_reader :time
+
+ # Accessor to the logger's name
+ attr_reader :name
+
+
+ def initialize( logger, options, *messages)
+ @time = Time.now
+ @name = logger.name
+
+ extract!(options)
+
+ @messages = messages
+
+ @caller = logger.trace.at?(level) ? caller[caller_index].to_s : ''
+ @file = nil
+ @line = nil
+ @method = nil
+
+ @pid = nil
+ end
+
+ # Accessor to the hostname
+ def hostname
+ @@hostname
+ end
+
+ # Accessor to the progname
+ def progname
+ @@progname
+ end
+
+ # Accessor to the PID
+ def pid
+ Process.pid
+ end
+
+ # Accessor to the thread's id
+ def thread_id
+ Thread.current.object_id
+ end
+
+ # Accessor to filename the log event occured
+ def file
+ @file || (backtrace!; @file)
+ end
+
+ # Accessor to the line the log event occured
+ def line
+ @line || (backtrace!; @line)
+ end
+
+ # Accessor to the method the log event occured
+ def method
+ @method || (backtrace!; @method)
+ end
+
+
+ private
+
+ def extract!( options )
+ if options.is_a?(Yell::Event::Options)
+ @level = options.severity
+ @caller_offset = options.caller_offset
+ else
+ @level = options
+ @caller_offset = 0
+ end
+ end
+
+ def caller_index
+ CallerIndex + @caller_offset
+ end
+
+ def backtrace!
+ if m = CallerRegexp.match(@caller)
+ @file, @line, @method = m[1..-1]
+ else
+ @file, @line, @method = ['', '', '']
+ end
+ end
+
+ end
+end
+
diff --git a/lib/yell/formatter.rb b/lib/yell/formatter.rb
new file mode 100644
index 0000000..e98edd0
--- /dev/null
+++ b/lib/yell/formatter.rb
@@ -0,0 +1,242 @@
+# encoding: utf-8
+require 'time'
+
+# TODO: Register custom formats
+#
+# @example The Yell default fomat
+# Yell::Formatter.register(:default)
+#
+# @example The Ruby standard logger format
+# Yell::Formatter.register(:stdlogger, "%l, [%d #%p] %5L -- : %m", "%Y-%m-%dT%H:%M:%S.%6N")
+#
+module Yell #:nodoc:
+
+ # No format on the log message
+ #
+ # @example
+ # logger = Yell.new STDOUT, :format => false
+ # logger.info "Hello World!"
+ # #=> "Hello World!"
+ NoFormat = "%m"
+
+ # Default Format
+ #
+ # @example
+ # logger = Yell.new STDOUT, :format => Yell::DefaultFormat
+ # logger.info "Hello World!"
+ # #=> "2012-02-29T09:30:00+01:00 [ INFO] 65784 : Hello World!"
+ # # ^ ^ ^ ^
+ # # ISO8601 Timestamp Level Pid Message
+ DefaultFormat = "%d [%5L] %p : %m"
+
+ # Basic Format
+ #
+ # @example
+ # logger = Yell.new STDOUT, :format => Yell::BasicFormat
+ # logger.info "Hello World!"
+ # #=> "I, 2012-02-29T09:30:00+01:00 : Hello World!"
+ # # ^ ^ ^
+ # # ^ ISO8601 Timestamp Message
+ # # Level (short)
+ BasicFormat = "%l, %d : %m"
+
+ # Extended Format
+ #
+ # @example
+ # logger = Yell.new STDOUT, :format => Yell::ExtendedFormat
+ # logger.info "Hello World!"
+ # #=> "2012-02-29T09:30:00+01:00 [ INFO] 65784 localhost : Hello World!"
+ # # ^ ^ ^ ^ ^
+ # # ISO8601 Timestamp Level Pid Hostname Message
+ ExtendedFormat = "%d [%5L] %p %h : %m"
+
+
+ # The +Formatter+ provides a handle to configure your log message style.
+ class Formatter
+
+ Table = {
+ "m" => "message(event.messages)", # Message
+ "l" => "level(event.level, 1)", # Level (short), e.g.'I', 'W'
+ "L" => "level(event.level)", # Level, e.g. 'INFO', 'WARN'
+ "d" => "date(event.time)", # ISO8601 Timestamp
+ "h" => "event.hostname", # Hostname
+ "p" => "event.pid", # PID
+ "P" => "event.progname", # Progname
+ "t" => "event.thread_id", # Thread ID
+ "F" => "event.file", # Path with filename where the logger was called
+ "f" => "File.basename(event.file)", # Filename where the loger was called
+ "M" => "event.method", # Method name where the logger was called
+ "n" => "event.line", # Line where the logger was called
+ "N" => "event.name" # Name of the logger
+ }
+
+ # For standard formatted backwards compatibility
+ LegacyTable = Hash[ Table.keys.map { |k| [k, 'noop'] } ].merge(
+ 'm' => 'message(msg)',
+ 'l' => 'level(event, 1)',
+ 'L' => 'level(event)',
+ 'd' => 'date(time)',
+ "p" => "$$",
+ 'P' => 'progname'
+ )
+
+ PatternMatcher = /([^%]*)(%\d*)?(#{Table.keys.join('|')})?(.*)/m
+
+
+ attr_reader :pattern, :date_pattern
+
+
+ # Initializes a new +Yell::Formatter+.
+ #
+ # Upon initialization it defines a format method. `format` takes
+ # a {Yell::Event} instance as agument in order to apply for desired log
+ # message formatting.
+ #
+ # @example Blank formatter
+ # Formatter.new
+ #
+ # @example Formatter with a message pattern
+ # Formatter.new("%d [%5L] %p : %m")
+ #
+ # @example Formatter with a message and date pattern
+ # Formatter.new("%d [%5L] %p : %m", "%D %H:%M:%S.%L")
+ #
+ # @example Formatter with a message modifier
+ # Formatter.new do |f|
+ # f.modify(Hash) { |h| "Hash: #{h.inspect}" }
+ # end
+ def initialize( *args, &block )
+ builder = Builder.new(*args, &block)
+
+ @pattern = builder.pattern
+ @date_pattern = builder.date_pattern
+ @modifier = builder.modifier
+
+ define_date_method!
+ define_call_method!
+ end
+
+ # Get a pretty string
+ def inspect
+ "#<#{self.class.name} pattern: #{@pattern.inspect}, date_pattern: #{@date_pattern.inspect}>"
+ end
+
+
+ private
+
+ # Message modifier class to allow different modifiers for different requirements.
+ class Modifier
+ def initialize
+ @repository = {}
+ end
+
+ def set( key, &block )
+ @repository.merge!(key => block)
+ end
+
+ def call( message )
+ case
+ when mod = @repository[message.class] || @repository[message.class.to_s]
+ mod.call(message)
+ when message.is_a?(Array)
+ message.map { |m| call(m) }.join(" ")
+ when message.is_a?(Hash)
+ message.map { |k, v| "#{k}: #{v}" }.join(", ")
+ when message.is_a?(Exception)
+ backtrace = message.backtrace ? "\n\t#{message.backtrace.join("\n\t")}" : ""
+ sprintf("%s: %s%s", message.class, message.message, backtrace)
+ else
+ message
+ end
+ end
+ end
+
+ # Builder class to allow setters that won't be accessible once
+ # transferred to the Formatter
+ class Builder
+ attr_accessor :pattern, :date_pattern
+ attr_reader :modifier
+
+ def initialize( pattern = nil, date_pattern = nil, &block )
+ @modifier = Modifier.new
+
+ @pattern = case pattern
+ when false then Yell::NoFormat
+ when nil then Yell::DefaultFormat
+ else pattern
+ end
+
+ @pattern << "\n" unless @pattern[-1] == ?\n # add newline if not present
+ @date_pattern = date_pattern || :iso8601
+
+ block.call(self) if block
+ end
+
+ def modify( key, &block )
+ modifier.set(key, &block)
+ end
+ end
+
+ def define_date_method!
+ buf = case @date_pattern
+ when String then "t.strftime(@date_pattern)"
+ when Symbol then respond_to?(@date_pattern, true) ? "#{@date_pattern}(t)" : "t.#{@date_pattern}"
+ else t.iso8601
+ end
+
+ # define the method
+ instance_eval <<-METHOD, __FILE__, __LINE__
+ def date(t = Time.now)
+ #{buf}
+ end
+ METHOD
+ end
+
+ # define a standard +Logger+ backwards compatible #call method for the formatter
+ def define_call_method!
+ instance_eval <<-METHOD, __FILE__, __LINE__
+ def call(event, time = nil, progname = nil, msg = nil)
+ event.is_a?(Yell::Event) ? #{to_sprintf(Table)} : #{to_sprintf(LegacyTable)}
+ end
+ METHOD
+ end
+
+ def to_sprintf( table )
+ buff, args, _pattern = "", [], @pattern.dup
+
+ while true
+ match = PatternMatcher.match(_pattern)
+
+ buff << match[1] unless match[1].empty?
+ break if match[2].nil?
+
+ buff << match[2] + 's'
+ args << table[ match[3] ]
+
+ _pattern = match[4]
+ end
+
+ %Q{sprintf("#{buff.gsub(/"/, '\"')}", #{args.join(', ')})}
+ end
+
+ def level( sev, length = nil )
+ severity = case sev
+ when Integer then Yell::Severities[sev] || 'ANY'
+ else sev
+ end
+
+ length.nil? ? severity : severity[0, length]
+ end
+
+ def message( messages )
+ @modifier.call(messages.is_a?(Array) && messages.size == 1 ? messages.first : messages)
+ end
+
+ # do nothing
+ def noop
+ ''
+ end
+
+ end
+end
+
diff --git a/lib/yell/helpers/adapter.rb b/lib/yell/helpers/adapter.rb
new file mode 100644
index 0000000..8fbe01c
--- /dev/null
+++ b/lib/yell/helpers/adapter.rb
@@ -0,0 +1,46 @@
+# encoding: utf-8
+module Yell #:nodoc:
+ module Helpers #:nodoc:
+ module Adapter #:nodoc:
+
+ # Define an adapter to be used for logging.
+ #
+ # @example Standard adapter
+ # adapter :file
+ #
+ # @example Standard adapter with filename
+ # adapter :file, 'development.log'
+ #
+ # # Alternative notation for filename in options
+ # adapter :file, :filename => 'developent.log'
+ #
+ # @example Standard adapter with filename and additional options
+ # adapter :file, 'development.log', :level => :warn
+ #
+ # @example Set the adapter directly from an adapter instance
+ # adapter Yell::Adapter::File.new
+ #
+ # @param [Symbol] type The type of the adapter, may be `:file` or `:datefile` (default `:file`)
+ # @return [Yell::Adapter] The instance
+ # @raise [Yell::NoSuchAdapter] Will be thrown when the adapter is not defined
+ def adapter( type = :file, *args, &block )
+ adapters.add(type, *args, &block)
+ end
+
+ def adapters
+ @__adapters__
+ end
+
+
+ private
+
+ def reset!
+ @__adapters__ = Yell::Adapters::Collection.new(@options)
+
+ super
+ end
+
+ end
+ end
+end
+
diff --git a/lib/yell/helpers/base.rb b/lib/yell/helpers/base.rb
new file mode 100644
index 0000000..d483c47
--- /dev/null
+++ b/lib/yell/helpers/base.rb
@@ -0,0 +1,19 @@
+# encoding: utf-8
+module Yell #:nodoc:
+ module Helpers #:nodoc:
+ module Base #:nodoc:
+
+ private
+
+ # stub
+ def reset!
+ end
+
+ def inspectables
+ []
+ end
+
+ end
+ end
+end
+
diff --git a/lib/yell/helpers/formatter.rb b/lib/yell/helpers/formatter.rb
new file mode 100644
index 0000000..c1c830d
--- /dev/null
+++ b/lib/yell/helpers/formatter.rb
@@ -0,0 +1,32 @@
+# encoding: utf-8
+module Yell #:nodoc:
+ module Helpers #:nodoc:
+ module Formatter #:nodoc:
+
+ # Set the format for your message.
+ def formatter=( pattern )
+ @__formatter__ = case pattern
+ when Yell::Formatter then pattern
+ else Yell::Formatter.new(*pattern)
+ end
+ end
+ alias :format= :formatter=
+
+ def formatter
+ @__formatter__
+ end
+ alias :format :formatter
+
+
+ private
+
+ def reset!
+ @__formatter__ = Yell::Formatter.new
+
+ super
+ end
+
+ end
+ end
+end
+
diff --git a/lib/yell/helpers/level.rb b/lib/yell/helpers/level.rb
new file mode 100644
index 0000000..9f63a65
--- /dev/null
+++ b/lib/yell/helpers/level.rb
@@ -0,0 +1,40 @@
+# encoding: utf-8
+module Yell #:nodoc:
+ module Helpers #:nodoc:
+ module Level
+
+ # Set the minimum log level.
+ #
+ # @example Set the level to :warn
+ # level = :warn
+ #
+ # @param [String, Symbol, Integer] severity The minimum log level
+ def level=( severity )
+ @__level__ = case severity
+ when Yell::Level then severity
+ else Yell::Level.new(severity)
+ end
+ end
+
+ # @private
+ def level
+ @__level__
+ end
+
+
+ private
+
+ def reset!
+ @__level__ = Yell::Level.new
+
+ super
+ end
+
+ def inspectables
+ [:level] | super
+ end
+
+ end
+ end
+end
+
diff --git a/lib/yell/helpers/silencer.rb b/lib/yell/helpers/silencer.rb
new file mode 100644
index 0000000..669ec84
--- /dev/null
+++ b/lib/yell/helpers/silencer.rb
@@ -0,0 +1,31 @@
+# encoding: utf-8
+module Yell #:nodoc:
+ module Helpers #:nodoc:
+ module Silencer
+
+ # Set the silence pattern
+ def silence( *patterns )
+ silencer.add(*patterns)
+ end
+
+ def silencer
+ @__silencer__
+ end
+
+
+ private
+
+ def reset!
+ @__silencer__ = Yell::Silencer.new
+
+ super
+ end
+
+ def silence!( *messages )
+ @__silencer__.silence!(*messages) if silencer.silence?
+ end
+
+ end
+ end
+end
+
diff --git a/lib/yell/helpers/tracer.rb b/lib/yell/helpers/tracer.rb
new file mode 100644
index 0000000..d1fad15
--- /dev/null
+++ b/lib/yell/helpers/tracer.rb
@@ -0,0 +1,48 @@
+# encoding: utf-8
+module Yell #:nodoc:
+ module Helpers #:nodoc:
+ module Tracer #:nodoc:
+
+ # Set whether the logger should allow tracing or not. The trace option
+ # will tell the logger when to provider caller information.
+ #
+ # @example No tracing at all
+ # trace = false
+ #
+ # @example Trace every time
+ # race = true
+ #
+ # @example Trace from the error level onwards
+ # trace = :error
+ # trace = 'gte.error'
+ #
+ # @return [Yell::Level] a level representation of the tracer
+ def trace=( severity )
+ @__trace__ = case severity
+ when Yell::Level then severity
+ when false then Yell::Level.new("gt.#{Yell::Severities.last}")
+ else Yell::Level.new(severity)
+ end
+ end
+
+ def trace
+ @__trace__
+ end
+
+
+ private
+
+ def reset!
+ @__trace__ = Yell::Level.new('gte.error')
+
+ super
+ end
+
+ def inspectables
+ [:trace] | super
+ end
+
+ end
+ end
+end
+
diff --git a/lib/yell/level.rb b/lib/yell/level.rb
new file mode 100644
index 0000000..e47c283
--- /dev/null
+++ b/lib/yell/level.rb
@@ -0,0 +1,214 @@
+# encoding: utf-8
+
+module Yell #:nodoc:
+
+ # The +Level+ class handles the severities for you in order to determine
+ # if an adapter should log or not.
+ #
+ # In order to setup your level, you have certain modifiers available:
+ # at :warn # will be set to :warn level only
+ # gt :warn # Will set from :error level onwards
+ # gte :warn # Will set from :warn level onwards
+ # lt :warn # Will set from :info level an below
+ # lte :warn # Will set from :warn level and below
+ #
+ # You are able to combine those modifiers to your convenience.
+ #
+ # @example Set from :info to :error (including)
+ # Yell::Level.new(:info).lte(:error)
+ #
+ # @example Set from :info to :error (excluding)
+ # Yell::Level.new(:info).lt(:error)
+ #
+ # @example Set at :info only
+ # Yell::Level.new.at(:info)
+ class Level
+ include Comparable
+
+ InterpretRegexp = /(at|gt|gte|lt|lte)?\.?(#{Yell::Severities.join('|')})/i
+
+ # Create a new level instance.
+ #
+ # @example Enable all severities
+ # Yell::Level.new
+ #
+ # @example Pass the minimum possible severity
+ # Yell::Level.new :warn
+ #
+ # @example Pass an array to exactly set the level at the given severities
+ # Yell::Level.new [:info, :error]
+ #
+ # @example Pass a range to set the level within the severities
+ # Yell::Level.new (:info..:error)
+ #
+ # @param [Integer,String,Symbol,Array,Range,nil] severity The severity for the level.
+ def initialize( *severities )
+ set(*severities)
+ end
+
+ # Set the severity to the given format
+ def set( *severities )
+ @severities = Yell::Severities.map { true }
+ severity = severities.length > 1 ? severities : severities.first
+
+ case severity
+ when Array then at(*severity)
+ when Range then gte(severity.first).lte(severity.last)
+ when String then interpret(severity)
+ when Integer, Symbol then gte(severity)
+ when Yell::Level then @severities = severity.severities
+ end
+ end
+
+ # Returns whether the level is allowed at the given severity
+ #
+ # @example
+ # at? :warn
+ # at? 0 # debug
+ #
+ # @return [Boolean] tru or false
+ def at?( severity )
+ index = index_from(severity)
+
+ index.nil? ? false : @severities[index]
+ end
+
+ # Set the level at specific severities
+ #
+ # @example Set at :debug and :error only
+ # at :debug, :error
+ #
+ # @return [Yell::Level] the instance
+ def at( *severities )
+ severities.each { |severity| calculate! :==, severity }
+ self
+ end
+
+ # Set the level to greater than the given severity
+ #
+ # @example Set to :error and above
+ # gt :warn
+ #
+ # @return [Yell::Level] the instance
+ def gt( severity )
+ calculate! :>, severity
+ self
+ end
+
+ # Set the level greater or equal to the given severity
+ #
+ # @example Set to :warn and above
+ # gte :warn
+ #
+ # @return [Yell::Level] the instance
+ def gte( severity )
+ calculate! :>=, severity
+ self
+ end
+
+ # Set the level lower than given severity
+ #
+ # @example Set to lower than :warn
+ # lt :warn
+ #
+ # @return [Yell::Level] the instance
+ def lt( severity )
+ calculate! :<, severity
+ self
+ end
+
+ # Set the level lower or equal than given severity
+ #
+ # @example Set to lower or equal than :warn
+ # lte :warn
+ #
+ # @return [Yell::Level] the instance
+ def lte( severity )
+ calculate! :<=, severity
+ self
+ end
+
+ # to_i implements backwards compatibility
+ def to_i
+ @severities.each_with_index { |s,i| return i if s == true }
+ end
+ alias :to_int :to_i
+
+ # Get a pretty string representation of the level, including the severities.
+ def inspect
+ inspectables = Yell::Severities.select.with_index { |l, i| !!@severities[i] }
+ "#<#{self.class.name} severities: #{inspectables * ', '}>"
+ end
+
+ # @private
+ def severities
+ @severities
+ end
+
+ # @private
+ def ==(other)
+ other.respond_to?(:severities) ? severities == other.severities : super
+ end
+
+ # @private
+ def <=>( other )
+ other.is_a?(Numeric) ? to_i <=> other : super
+ end
+
+
+ private
+
+ def interpret( severities )
+ severities.split( ' ' ).each do |severity|
+ if m = InterpretRegexp.match(severity)
+ m[1].nil? ? __send__( :gte, m[2] ) : __send__( m[1], m[2] )
+ end
+ end
+ end
+
+ def calculate!( modifier, severity )
+ index = index_from(severity)
+ return if index.nil?
+
+ case modifier
+ when :> then ascending!( index+1 )
+ when :>= then ascending!( index )
+ when :< then descending!( index-1 )
+ when :<= then descending!( index )
+ else set!( index ) # :==
+ end
+
+ taint unless tainted?
+ end
+
+ def index_from( severity )
+ case severity
+ when String, Symbol then Yell::Severities.index(severity.to_s.upcase)
+ else Integer(severity)
+ end
+ end
+
+ def ascending!( index )
+ each { |s, i| @severities[i] = i < index ? false : true }
+ end
+
+ def descending!( index )
+ each { |s, i| @severities[i] = index < i ? false : true }
+ end
+
+ def each
+ @severities.each_with_index do |s, i|
+ next if s == false # skip
+
+ yield(s, i)
+ end
+ end
+
+ def set!( index, val = true )
+ @severities.map! { false } unless tainted?
+
+ @severities[index] = val
+ end
+
+ end
+end
diff --git a/lib/yell/loggable.rb b/lib/yell/loggable.rb
new file mode 100644
index 0000000..05ae0bf
--- /dev/null
+++ b/lib/yell/loggable.rb
@@ -0,0 +1,37 @@
+# encoding: utf-8
+
+module Yell #:nodoc:
+
+ # Include this module to add a logger to any class.
+ #
+ # When including this module, your class will have a :logger instance method
+ # available. Before you can use it, you will need to define a Yell logger and
+ # provide it with the name of your class.
+ #
+ # @example
+ # Yell.new :stdout, :name => 'Foo'
+ #
+ # class Foo
+ # include Yell::Loggable
+ # end
+ #
+ # Foo.new.logger.info "Hello World"
+ module Loggable
+
+ def self.included(base)
+ base.extend(ClassMethods)
+ end
+
+ module ClassMethods
+ def logger
+ Yell[self]
+ end
+ end
+
+ def logger
+ self.class.logger
+ end
+
+ end
+end
+
diff --git a/lib/yell/logger.rb b/lib/yell/logger.rb
new file mode 100644
index 0000000..30bb7ee
--- /dev/null
+++ b/lib/yell/logger.rb
@@ -0,0 +1,163 @@
+# encoding: utf-8
+
+require 'pathname'
+
+module Yell #:nodoc:
+
+ # The +Yell::Logger+ is your entrypoint. Anything onwards is derived from here.
+ #
+ # A +Yell::Logger+ instance holds all your adapters and sends the log events
+ # to them if applicable. There are multiple ways of how to create a new logger.
+ class Logger
+ include Yell::Helpers::Base
+ include Yell::Helpers::Level
+ include Yell::Helpers::Formatter
+ include Yell::Helpers::Adapter
+ include Yell::Helpers::Tracer
+ include Yell::Helpers::Silencer
+
+ # The name of the logger instance
+ attr_reader :name
+
+ # Initialize a new Logger
+ #
+ # @example A standard file logger
+ # Yell::Logger.new 'development.log'
+ #
+ # @example A standard datefile logger
+ # Yell::Logger.new :datefile
+ # Yell::Logger.new :datefile, 'development.log'
+ #
+ # @example Setting the log level
+ # Yell::Logger.new :level => :warn
+ #
+ # Yell::Logger.new do |l|
+ # l.level = :warn
+ # end
+ #
+ # @example Combined settings
+ # Yell::Logger.new 'development.log', :level => :warn
+ #
+ # Yell::Logger.new :datefile, 'development.log' do |l|
+ # l.level = :info
+ # end
+ def initialize( *args, &block )
+ # extract options
+ @options = args.last.is_a?(Hash) ? args.pop : {}
+
+ # check if filename was given as argument and put it into the @options
+ if [String, Pathname].include?(args.last.class)
+ @options[:filename] = args.pop unless @options[:filename]
+ end
+
+ reset!
+
+ # FIXME: :format is deprecated in future versions --R
+ self.formatter = Yell.__fetch__(@options, :format, :formatter)
+ self.level = Yell.__fetch__(@options, :level, :default => 0)
+ self.name = Yell.__fetch__(@options, :name)
+ self.trace = Yell.__fetch__(@options, :trace, :default => :error)
+
+ # silencer
+ self.silence(*Yell.__fetch__(@options, :silence, :default => []))
+
+ # adapters may be passed in the options
+ extract!(*Yell.__fetch__(@options, :adapters, :default => []))
+
+ # extract adapter
+ self.adapter(args.pop) if args.any?
+
+ # eval the given block
+ block.arity > 0 ? block.call(self) : instance_eval(&block) if block_given?
+
+ # default adapter when none defined
+ self.adapter(:file) if adapters.empty?
+ end
+
+
+ # Set the name of a logger. When providing a name, the logger will
+ # automatically be added to the Yell::Repository.
+ #
+ # @return [String] The logger's name
+ def name=( val )
+ Yell::Repository[val] = self if val
+ @name = val.nil? ? "<#{self.class.name}##{object_id}>": val
+
+ @name
+ end
+
+ # Somewhat backwards compatible method (not fully though)
+ def add( options, *messages, &block )
+ return false unless level.at?(options)
+
+ messages = messages
+ messages << block.call unless block.nil?
+ messages = silencer.call(*messages)
+ return false if messages.empty?
+
+ event = Yell::Event.new(self, options, *messages)
+ write(event)
+ end
+
+ # Creates instance methods for every log level:
+ # `debug` and `debug?`
+ # `info` and `info?`
+ # `warn` and `warn?`
+ # `error` and `error?`
+ # `unknown` and `unknown?`
+ Yell::Severities.each_with_index do |s, index|
+ name = s.downcase
+
+ class_eval <<-EOS, __FILE__, __LINE__ + index
+ def #{name}?; level.at?(#{index}); end # def info?; level.at?(1); end
+ #
+ def #{name}( *m, &b ) # def info( *m, &b )
+ options = Yell::Event::Options.new(#{index}, 1)
+ add(options, *m, &b) # add(Yell::Event::Options.new(1, 1), *m, &b)
+ end # end
+ EOS
+ end
+
+ # Get a pretty string representation of the logger.
+ def inspect
+ inspection = inspectables.map { |m| "#{m}: #{send(m).inspect}" }
+ "#<#{self.class.name} #{inspection * ', '}>"
+ end
+
+ # @private
+ def close
+ adapters.close
+ end
+
+ # @private
+ def write( event )
+ adapters.write(event)
+ end
+
+ private
+
+ # The :adapters key may be passed to the options hash. It may appear in
+ # multiple variations:
+ #
+ # @example
+ # extract!(:stdout, :stderr)
+ #
+ # @example
+ # extract!(:stdout => {:level => :info}, :stderr => {:level => :error})
+ def extract!( *adapters )
+ adapters.each do |a|
+ case a
+ when Hash then a.each { |t, o| adapter(t, o) }
+ else adapter(a)
+ end
+ end
+ end
+
+ # Get an array of inspected attributes for the adapter.
+ def inspectables
+ [:name] | super
+ end
+
+ end
+end
+
diff --git a/lib/yell/repository.rb b/lib/yell/repository.rb
new file mode 100644
index 0000000..5461e2a
--- /dev/null
+++ b/lib/yell/repository.rb
@@ -0,0 +1,72 @@
+# encoding: utf-8
+
+require 'monitor'
+require "singleton"
+
+module Yell #:nodoc:
+
+ class LoggerNotFound < StandardError
+ def message; "Could not find a Yell::Logger instance with the name '#{super}'"; end
+ end
+
+ class Repository
+ extend MonitorMixin
+ include Singleton
+
+ def initialize
+ @loggers = {}
+ end
+
+ # Set loggers in the repository
+ #
+ # @example Set a logger
+ # Yell::Repository[ 'development' ] = Yell::Logger.new :stdout
+ #
+ # @return [Yell::Logger] The logger instance
+ def self.[]=( name, logger )
+ synchronize { instance.loggers[name] = logger }
+ end
+
+ # Get loggers from the repository
+ #
+ # @example Get the logger
+ # Yell::Repository[ 'development' ]
+ #
+ # @raise [Yell::LoggerNotFound] Raised when repository does not have that key
+ # @return [Yell::Logger] The logger instance
+ def self.[]( name )
+ synchronize { instance.__fetch__(name) or raise Yell::LoggerNotFound.new(name) }
+ end
+
+ # Get the list of all loggers in the repository
+ #
+ # @return [Hash] The map of loggers
+ def self.loggers
+ synchronize { instance.loggers }
+ end
+
+ # @private
+ def loggers
+ @loggers
+ end
+
+ # @private
+ #
+ # Fetch the logger by the given name.
+ #
+ # If the logger could not be found and has a superclass, it
+ # will attempt to look there. This is important for the
+ # Yell::Loggable module.
+ def __fetch__( name )
+ logger = loggers[name] || loggers[name.to_s]
+
+ if logger.nil? && name.respond_to?(:superclass)
+ return __fetch__(name.superclass)
+ end
+
+ logger
+ end
+
+ end
+end
+
diff --git a/lib/yell/silencer.rb b/lib/yell/silencer.rb
new file mode 100644
index 0000000..e371bbd
--- /dev/null
+++ b/lib/yell/silencer.rb
@@ -0,0 +1,86 @@
+# encoding: utf-8
+module Yell #:nodoc:
+
+ # The +Yell::Silencer+ is your handly helper for stiping out unwanted log messages.
+ class Silencer
+
+ class PresetNotFound < StandardError
+ def message; "Could not find a preset for #{super.inspect}"; end
+ end
+
+ Presets = {
+ :assets => [/\AStarted GET "\/assets/, /\AServed asset/, /\A\s*\z/] # for Rails
+ }
+
+
+ def initialize( *patterns )
+ @patterns = patterns.dup
+ end
+
+ # Add one or more patterns to the silencer
+ #
+ # @example
+ # add( 'password' )
+ # add( 'username', 'password' )
+ #
+ # @example Add regular expressions
+ # add( /password/ )
+ #
+ # @return [self] The silencer instance
+ def add( *patterns )
+ patterns.each { |pattern| add!(pattern) }
+
+ self
+ end
+
+ # Clears out all the messages that would match any defined pattern
+ #
+ # @example
+ # call(['username', 'password'])
+ # #=> ['username]
+ #
+ # @return [Array] The remaining messages
+ def call( *messages )
+ return messages if @patterns.empty?
+
+ messages.reject { |m| matches?(m) }
+ end
+
+ # Get a pretty string
+ def inspect
+ "#<#{self.class.name} patterns: #{@patterns.inspect}>"
+ end
+
+ # @private
+ def patterns
+ @patterns
+ end
+
+
+ private
+
+ def add!( pattern )
+ @patterns = @patterns | fetch(pattern)
+ end
+
+ def fetch( pattern )
+ case pattern
+ when Symbol then Presets[pattern] or raise PresetNotFound.new(pattern)
+ else [pattern]
+ end
+ end
+
+ # Check if the provided message matches any of the defined patterns.
+ #
+ # @example
+ # matches?('password')
+ # #=> true
+ #
+ # @return [Boolean] true or false
+ def matches?( message )
+ @patterns.any? { |pattern| message.respond_to?(:match) && message.match(pattern) }
+ end
+
+ end
+end
+
diff --git a/lib/yell/version.rb b/lib/yell/version.rb
new file mode 100644
index 0000000..3bdbd3d
--- /dev/null
+++ b/lib/yell/version.rb
@@ -0,0 +1,7 @@
+# encoding: utf-8
+
+module Yell #:nodoc:
+ VERSION = "2.0.5"
+
+end
+
diff --git a/metadata.yml b/metadata.yml
new file mode 100644
index 0000000..cffbf32
--- /dev/null
+++ b/metadata.yml
@@ -0,0 +1,130 @@
+--- !ruby/object:Gem::Specification
+name: yell
+version: !ruby/object:Gem::Version
+ version: 2.0.5
+platform: ruby
+authors:
+- Rudolf Schmidt
+autorequire:
+bindir: bin
+cert_chain: []
+date: 2014-10-14 00:00:00.000000000 Z
+dependencies: []
+description: Yell - Your Extensible Logging Library. Define multiple adapters, various
+ log level combinations or message formatting options like you've never done before
+email:
+executables: []
+extensions: []
+extra_rdoc_files: []
+files:
+- ".gitignore"
+- ".travis.yml"
+- Gemfile
+- LICENSE.txt
+- README.md
+- Rakefile
+- examples/001-basic-usage.rb
+- examples/002.1-log-level-basics.rb
+- examples/002.2-log-level-on-certain-severities-only.rb
+- examples/002.3-log-level-within-range.rb
+- examples/003.1-formatting-DefaultFormat.rb
+- examples/003.2-formatting-BasicFormat.rb
+- examples/003.3-formatting-ExtendedFormat.rb
+- examples/003.4-formatting-on-your-own.rb
+- examples/004.1-colorizing-the-log-output.rb
+- examples/005.1-repository.rb
+- examples/006.1-the-loggable-module.rb
+- examples/006.2-the-loggable-module-with-inheritance.rb
+- lib/core_ext/logger.rb
+- lib/yell.rb
+- lib/yell/adapters.rb
+- lib/yell/adapters/base.rb
+- lib/yell/adapters/datefile.rb
+- lib/yell/adapters/file.rb
+- lib/yell/adapters/io.rb
+- lib/yell/adapters/streams.rb
+- lib/yell/configuration.rb
+- lib/yell/event.rb
+- lib/yell/formatter.rb
+- lib/yell/helpers/adapter.rb
+- lib/yell/helpers/base.rb
+- lib/yell/helpers/formatter.rb
+- lib/yell/helpers/level.rb
+- lib/yell/helpers/silencer.rb
+- lib/yell/helpers/tracer.rb
+- lib/yell/level.rb
+- lib/yell/loggable.rb
+- lib/yell/logger.rb
+- lib/yell/repository.rb
+- lib/yell/silencer.rb
+- lib/yell/version.rb
+- spec/compatibility/activesupport_logger_spec.rb
+- spec/compatibility/formatter_spec.rb
+- spec/compatibility/level_spec.rb
+- spec/fixtures/yell.yml
+- spec/spec_helper.rb
+- spec/threaded/yell_spec.rb
+- spec/yell/adapters/base_spec.rb
+- spec/yell/adapters/datefile_spec.rb
+- spec/yell/adapters/file_spec.rb
+- spec/yell/adapters/io_spec.rb
+- spec/yell/adapters/streams_spec.rb
+- spec/yell/adapters_spec.rb
+- spec/yell/configuration_spec.rb
+- spec/yell/dsl_spec.rb
+- spec/yell/event_spec.rb
+- spec/yell/formatter_spec.rb
+- spec/yell/level_spec.rb
+- spec/yell/loggable_spec.rb
+- spec/yell/logger_spec.rb
+- spec/yell/repository_spec.rb
+- spec/yell/silencer_spec.rb
+- spec/yell_spec.rb
+- yell.gemspec
+homepage: http://rudionrailspec.github.com/yell
+licenses:
+- MIT
+metadata: {}
+post_install_message:
+rdoc_options: []
+require_paths:
+- lib
+required_ruby_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: '0'
+required_rubygems_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: '0'
+requirements: []
+rubyforge_project: yell
+rubygems_version: 2.2.2
+signing_key:
+specification_version: 4
+summary: Yell - Your Extensible Logging Library
+test_files:
+- spec/compatibility/activesupport_logger_spec.rb
+- spec/compatibility/formatter_spec.rb
+- spec/compatibility/level_spec.rb
+- spec/fixtures/yell.yml
+- spec/spec_helper.rb
+- spec/threaded/yell_spec.rb
+- spec/yell/adapters/base_spec.rb
+- spec/yell/adapters/datefile_spec.rb
+- spec/yell/adapters/file_spec.rb
+- spec/yell/adapters/io_spec.rb
+- spec/yell/adapters/streams_spec.rb
+- spec/yell/adapters_spec.rb
+- spec/yell/configuration_spec.rb
+- spec/yell/dsl_spec.rb
+- spec/yell/event_spec.rb
+- spec/yell/formatter_spec.rb
+- spec/yell/level_spec.rb
+- spec/yell/loggable_spec.rb
+- spec/yell/logger_spec.rb
+- spec/yell/repository_spec.rb
+- spec/yell/silencer_spec.rb
+- spec/yell_spec.rb
diff --git a/spec/compatibility/activesupport_logger_spec.rb b/spec/compatibility/activesupport_logger_spec.rb
new file mode 100644
index 0000000..7ff24ae
--- /dev/null
+++ b/spec/compatibility/activesupport_logger_spec.rb
@@ -0,0 +1,35 @@
+# encoding: utf-8
+require 'spec_helper'
+
+begin
+ require 'active_support'
+rescue LoadError
+end
+
+# make a setup just like in railties ~> 4.0.0
+#
+# We simulate the case when Rails 4 starts up its server
+# and wants to append the log output.
+describe "Compatibility to ActiveSupport::Logger", :pending => (!defined?(ActiveSupport) || ActiveSupport::VERSION::MAJOR < 4) do
+
+ let!(:yell) { Yell.new($stdout, :format => "%m") }
+
+ let!(:logger) do
+ console = ActiveSupport::Logger.new($stdout)
+ console.formatter = yell.formatter
+ console.level = yell.level
+
+ yell.extend(ActiveSupport::Logger.broadcast(console))
+
+ console
+ end
+
+ it "should behave correctly" do
+ mock($stdout).syswrite("Hello World\n") # yell
+ mock($stdout).write("Hello World\n") # logger
+
+ yell.info "Hello World"
+ end
+
+end
+
diff --git a/spec/compatibility/formatter_spec.rb b/spec/compatibility/formatter_spec.rb
new file mode 100644
index 0000000..00a4484
--- /dev/null
+++ b/spec/compatibility/formatter_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+require 'logger'
+
+describe "backwards compatible formatter" do
+
+ let(:time) { Time.now }
+ let(:formatter) { Yell::Formatter.new(Yell::DefaultFormat) }
+ let(:logger) { Logger.new($stdout) }
+
+ before do
+ Timecop.freeze(time)
+
+ logger.formatter = formatter
+ end
+
+ it "should format out the message correctly" do
+ mock($stdout).write("#{time.iso8601} [ INFO] #{$$} : Hello World!\n")
+
+ logger.info "Hello World!"
+ end
+
+end
+
diff --git a/spec/compatibility/level_spec.rb b/spec/compatibility/level_spec.rb
new file mode 100644
index 0000000..42aebff
--- /dev/null
+++ b/spec/compatibility/level_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+require 'logger'
+
+describe "backwards compatible level" do
+
+ let(:level) { Yell::Level.new(:error) }
+ let(:logger) { Logger.new($stdout) }
+
+ before do
+ logger.level = level
+ end
+
+ it "should format out the level correctly" do
+ expect(logger.level).to eq(level.to_i)
+ end
+
+end
+
diff --git a/spec/fixtures/yell.yml b/spec/fixtures/yell.yml
new file mode 100644
index 0000000..7de4ee9
--- /dev/null
+++ b/spec/fixtures/yell.yml
@@ -0,0 +1,7 @@
+test:
+ :level: info
+
+ :adapters:
+ - :stdout
+ - :stderr:
+ :level: "gte.error"
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
new file mode 100644
index 0000000..7681f88
--- /dev/null
+++ b/spec/spec_helper.rb
@@ -0,0 +1,56 @@
+$:.unshift File.expand_path('..', __FILE__)
+$:.unshift File.expand_path('../../lib', __FILE__)
+
+ENV['YELL_ENV'] = 'test'
+
+require 'rspec/core'
+require 'rspec/expectations'
+require 'rr'
+require 'timecop'
+
+begin
+ require 'pry'
+rescue LoadError
+end
+
+begin
+ require 'coveralls'
+ require 'simplecov'
+
+ STDERR.puts "Running coverage..."
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
+ SimpleCov::Formatter::HTMLFormatter,
+ Coveralls::SimpleCov::Formatter
+ ]
+
+ SimpleCov.start do
+ add_filter 'spec'
+ end
+rescue LoadError
+ # do nothing
+end
+
+require 'yell'
+
+RSpec.configure do |config|
+ config.mock_framework = :rr
+
+ config.before :each do
+ Yell::Repository.loggers.clear
+
+ Dir[ fixture_path + "/*.log" ].each { |f| File.delete f }
+ end
+
+ config.after :each do
+ Timecop.return # release time after each test
+ end
+
+
+ private
+
+ def fixture_path
+ File.expand_path( "fixtures", File.dirname(__FILE__) )
+ end
+
+end
+
diff --git a/spec/threaded/yell_spec.rb b/spec/threaded/yell_spec.rb
new file mode 100644
index 0000000..1e1d6d8
--- /dev/null
+++ b/spec/threaded/yell_spec.rb
@@ -0,0 +1,101 @@
+require 'spec_helper'
+
+describe "running Yell multi-threaded" do
+ let( :threads ) { 100 }
+ let( :range ) { (1..threads) }
+
+ let( :filename ) { fixture_path + '/threaded.log' }
+ let( :lines ) { `wc -l #{filename}`.to_i }
+
+ context "one instance" do
+ before do
+ logger = Yell.new filename
+
+ range.map do |count|
+ Thread.new { 10.times { logger.info count } }
+ end.each(&:join)
+
+ sleep 0.5
+ end
+
+ it "should write all messages" do
+ lines.should == 10*threads
+ end
+ end
+
+ # context "one instance per thread" do
+ # before do
+ # range.map do |count|
+ # Thread.new do
+ # logger = Yell.new( filename )
+
+ # 10.times { logger.info count }
+ # end
+ # end.each(&:join)
+
+ # sleep 0.5
+ # end
+
+ # it "should write all messages" do
+ # lines.should == 10*threads
+ # end
+ # end
+
+ context "one instance in the repository" do
+ before do
+ Yell[ 'threaded' ] = Yell.new( filename )
+ end
+
+ it "should write all messages" do
+ range.map do |count|
+ Thread.new { 10.times { Yell['threaded'].info count } }
+ end.each(&:join)
+
+ lines.should == 10*threads
+ end
+ end
+
+ context "multiple datefile instances" do
+ let( :threadlist ) { [] }
+ let( :date ) { Time.now }
+
+ before do
+ Timecop.freeze( date - 86400 )
+
+ range.each do |count|
+ threadlist << Thread.new do
+ logger = Yell.new :datefile, :filename => filename, :keep => 2
+ loop { logger.info :info; sleep 0.1 }
+ end
+ end
+
+ sleep 0.3 # sleep to get some messages into the file
+ end
+
+ after do
+ threadlist.each(&:kill)
+ end
+
+ it "should safely rollover" do
+ # now cycle the days
+ 7.times do |count|
+ Timecop.freeze( date + 86400*count )
+ sleep 0.3
+
+ files = Dir[ fixture_path + '/*.*.log' ]
+ files.size.should == 2
+
+ # files.last.should match( datefile_pattern_for(Time.now) ) # today
+ # files.first.should match( datefile_pattern_for(Time.now-86400) ) # yesterday
+ end
+ end
+ end
+
+ private
+
+ def datefile_pattern_for( time )
+ time.strftime(Yell::Adapters::Datefile::DefaultDatePattern)
+ end
+
+end
+
diff --git a/spec/yell/adapters/base_spec.rb b/spec/yell/adapters/base_spec.rb
new file mode 100644
index 0000000..d0e62c0
--- /dev/null
+++ b/spec/yell/adapters/base_spec.rb
@@ -0,0 +1,43 @@
+require 'spec_helper'
+
+describe Yell::Adapters::Base do
+
+ context "initialize" do
+ context ":level" do
+ let(:level) { Yell::Level.new(:warn) }
+
+ it "should set the level" do
+ adapter = Yell::Adapters::Base.new(:level => level)
+
+ expect(adapter.level).to eq(level)
+ end
+
+ it "should set the level when block was given" do
+ adapter = Yell::Adapters::Base.new { |a| a.level = level }
+
+ expect(adapter.level).to eq(level)
+ end
+ end
+ end
+
+ context "#write" do
+ let(:logger) { Yell::Logger.new }
+ subject { Yell::Adapters::Base.new(:level => 1) }
+
+ it "should delegate :event to :write!" do
+ event = Yell::Event.new(logger, 1, "Hello World!")
+ mock(subject).write!(event)
+
+ subject.write(event)
+ end
+
+ it "should not write when event does not have the right level" do
+ event = Yell::Event.new(logger, 0, "Hello World!")
+ dont_allow(subject).write!(event)
+
+ subject.write(event)
+ end
+ end
+
+end
+
diff --git a/spec/yell/adapters/datefile_spec.rb b/spec/yell/adapters/datefile_spec.rb
new file mode 100644
index 0000000..e1d30d7
--- /dev/null
+++ b/spec/yell/adapters/datefile_spec.rb
@@ -0,0 +1,168 @@
+require 'spec_helper'
+
+describe Yell::Adapters::Datefile do
+ let(:logger) { Yell::Logger.new }
+ let(:message) { "Hello World" }
+ let(:event) { Yell::Event.new(logger, 1, message) }
+
+ let(:today) { Time.now }
+ let(:tomorrow) { Time.now + 86400 }
+
+ let(:filename) { fixture_path + '/test.log' }
+ let(:today_filename) { fixture_path + "/test.#{today.strftime(Yell::Adapters::Datefile::DefaultDatePattern)}.log" }
+ let(:tomorrow_filename) { fixture_path + "/test.#{tomorrow.strftime(Yell::Adapters::Datefile::DefaultDatePattern)}.log" }
+
+ let(:adapter) { Yell::Adapters::Datefile.new(:filename => filename, :format => "%m") }
+
+ before do
+ Timecop.freeze(today)
+ end
+
+ it { should be_kind_of Yell::Adapters::File }
+
+ describe "#write" do
+ let(:today_lines) { File.readlines(today_filename) }
+
+ before do
+ adapter.write(event)
+ end
+
+ it "should be output to filename with date pattern" do
+ expect(File.exist?(today_filename)).to be_true
+
+ expect(today_lines.size).to eq(2) # includes header line
+ expect(today_lines.last).to match(message)
+ end
+
+ it "should output to the same file" do
+ adapter.write(event)
+
+ expect(File.exist?(today_filename)).to be_true
+ expect(today_lines.size).to eq(3) # includes header line
+ end
+
+ it "should not open file handle again" do
+ dont_allow(File).open(anything, anything)
+
+ adapter.write(event)
+ end
+
+ context "on rollover" do
+ let(:tomorrow_lines) { File.readlines(tomorrow_filename) }
+
+ before do
+ Timecop.freeze(tomorrow) { adapter.write(event) }
+ end
+
+ it "should rotate file" do
+ expect(File.exist?(tomorrow_filename)).to be_true
+
+ expect(tomorrow_lines.size).to eq(2) # includes header line
+ expect(tomorrow_lines.last).to match(message)
+ end
+ end
+ end
+
+ describe "#keep" do
+ before do
+ adapter.symlink = false # to not taint the Dir
+ adapter.keep = 2
+
+ adapter.write(event)
+ end
+
+ it "should keep the specified number or files upon rollover" do
+ expect(Dir[fixture_path + '/*.log'].size).to eq(1)
+
+ Timecop.freeze(tomorrow) { adapter.write(event) }
+ expect(Dir[fixture_path + '/*.log'].size).to eq(2)
+
+ Timecop.freeze(tomorrow + 86400 ) { adapter.write(event) }
+ expect(Dir[fixture_path + '/*.log'].size).to eq(2)
+ end
+ end
+
+ describe "#symlink" do
+ context "when true (default)" do
+ before do
+ adapter.write(event)
+ end
+
+ it "should be created on the original filename" do
+ expect(File.symlink?(filename)).to be_true
+ expect(File.readlink(filename)).to eq(today_filename)
+ end
+
+ it "should be recreated upon rollover" do
+ Timecop.freeze(tomorrow) { adapter.write(event) }
+
+ expect(File.symlink?(filename)).to be_true
+ expect(File.readlink(filename)).to eq(tomorrow_filename)
+ end
+ end
+
+ context "when false" do
+ before do
+ adapter.symlink = false
+ end
+
+ it "should not create the sylink the original filename" do
+ adapter.write( event )
+
+ expect(File.symlink?(filename)).to be_false
+ end
+ end
+ end
+
+ describe "#header" do
+ let(:header) { File.open(today_filename, &:readline) }
+
+ context "when true (default)" do
+ before do
+ adapter.write(event)
+ end
+
+ it "should be written" do
+ expect(header).to match(Yell::Adapters::Datefile::HeaderRegexp)
+ end
+
+ it "should be rewritten upon rollover" do
+ Timecop.freeze(tomorrow) { adapter.write(event) }
+
+ expect(File.symlink?(filename)).to be_true
+ expect(File.readlink(filename)).to eq(tomorrow_filename)
+ end
+ end
+
+ context "when false" do
+ before do
+ adapter.header = false
+ end
+
+ it "should not be written" do
+ adapter.write(event)
+
+ expect(header).to eq("Hello World\n")
+ end
+ end
+ end
+
+ context "another adapter with the same :filename" do
+ let(:another_adapter) { Yell::Adapters::Datefile.new(:filename => filename) }
+
+ before do
+ adapter.write(event)
+ end
+
+ it "should not write the header again" do
+ another_adapter.write(event)
+
+ # 1: header
+ # 2: adapter write
+ # 3: another_adapter: write
+ expect(File.readlines(today_filename).size).to eq(3)
+ end
+ end
+
+end
+
diff --git a/spec/yell/adapters/file_spec.rb b/spec/yell/adapters/file_spec.rb
new file mode 100644
index 0000000..785cb80
--- /dev/null
+++ b/spec/yell/adapters/file_spec.rb
@@ -0,0 +1,75 @@
+require 'spec_helper'
+
+describe Yell::Adapters::File do
+ let(:devnull) { File.new('/dev/null', 'w') }
+
+ before do
+ stub(File).open(anything, anything) { devnull }
+ end
+
+ it { should be_kind_of(Yell::Adapters::Io) }
+
+ context "#stream" do
+ subject { Yell::Adapters::File.new.send(:stream) }
+
+ it { should be_kind_of(File) }
+ end
+
+ context "#write" do
+ let(:logger) { Yell::Logger.new }
+ let(:event) { Yell::Event.new(logger, 1, "Hello World") }
+
+ context "default filename" do
+ let(:filename) { File.expand_path "#{Yell.env}.log" }
+ let(:adapter) { Yell::Adapters::File.new }
+
+ it "should print to file" do
+ mock(File).open(filename, File::WRONLY|File::APPEND|File::CREAT) { devnull }
+
+ adapter.write(event)
+ end
+ end
+
+ context "with given :filename" do
+ let(:filename) { fixture_path + '/filename.log' }
+ let(:adapter) { Yell::Adapters::File.new(:filename => filename) }
+
+ it "should print to file" do
+ mock(File).open(filename, File::WRONLY|File::APPEND|File::CREAT) { devnull }
+
+ adapter.write(event)
+ end
+ end
+
+ context "with given :pathname" do
+ let(:pathname) { Pathname.new(fixture_path).join('filename.log') }
+ let(:adapter) { Yell::Adapters::File.new( :filename => pathname ) }
+
+ it "should accept pathanme as filename" do
+ mock(File).open(pathname.to_s, File::WRONLY|File::APPEND|File::CREAT) { devnull }
+
+ adapter.write(event)
+ end
+ end
+
+ context "#sync" do
+ let(:adapter) { Yell::Adapters::File.new }
+
+ it "should sync by default" do
+ mock(devnull).sync=(true)
+
+ adapter.write(event)
+ end
+
+ it "pass the option to File" do
+ adapter.sync = false
+
+ mock(devnull).sync=(false)
+
+ adapter.write(event)
+ end
+ end
+ end
+
+end
+
diff --git a/spec/yell/adapters/io_spec.rb b/spec/yell/adapters/io_spec.rb
new file mode 100644
index 0000000..5f26642
--- /dev/null
+++ b/spec/yell/adapters/io_spec.rb
@@ -0,0 +1,72 @@
+require 'spec_helper'
+
+describe Yell::Adapters::Io do
+
+ it { should be_kind_of Yell::Adapters::Base }
+
+ context "initialize" do
+ it "should set default :format" do
+ adapter = Yell::Adapters::Io.new
+
+ expect(adapter.format).to be_kind_of(Yell::Formatter)
+ end
+
+ context ":level" do
+ let(:level) { Yell::Level.new(:warn) }
+
+ it "should set the level" do
+ adapter = Yell::Adapters::Io.new(:level => level)
+
+ expect(adapter.level).to eq(level)
+ end
+
+ it "should set the level when block was given" do
+ adapter = Yell::Adapters::Io.new { |a| a.level = level }
+
+ expect(adapter.level).to eq(level)
+ end
+ end
+
+ context ":format" do
+ let(:format) { Yell::Formatter.new }
+
+ it "should set the level" do
+ adapter = Yell::Adapters::Io.new(:format => format)
+
+ expect(adapter.format).to eq(format)
+ end
+
+ it "should set the level when block was given" do
+ adapter = Yell::Adapters::Io.new { |a| a.format = format }
+
+ expect(adapter.format).to eq(format)
+ end
+ end
+ end
+
+ context "#write" do
+ let(:logger) { Yell::Logger.new }
+ let(:event) { Yell::Event.new(logger, 1, "Hello World") }
+ let(:adapter) { Yell::Adapters::Io.new }
+ let(:stream) { File.new('/dev/null', 'w') }
+
+ before do
+ stub(adapter).stream { stream }
+ end
+
+ it "should format the message" do
+ mock.proxy(adapter.format).call( event )
+
+ adapter.write(event)
+ end
+
+ it "should print formatted message to stream" do
+ formatted = Yell::Formatter.new.call(event)
+ mock(stream).syswrite(formatted)
+
+ adapter.write(event)
+ end
+ end
+
+end
+
diff --git a/spec/yell/adapters/streams_spec.rb b/spec/yell/adapters/streams_spec.rb
new file mode 100644
index 0000000..1dd9095
--- /dev/null
+++ b/spec/yell/adapters/streams_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe Yell::Adapters::Stdout do
+
+ it { should be_kind_of(Yell::Adapters::Io) }
+
+ context "#stream" do
+ subject { Yell::Adapters::Stdout.new.send :stream }
+
+ it { should be_kind_of(IO) }
+ end
+
+end
+
+describe Yell::Adapters::Stderr do
+
+ it { should be_kind_of(Yell::Adapters::Io) }
+
+ context "#stream" do
+ subject { Yell::Adapters::Stderr.new.send(:stream) }
+
+ it { should be_kind_of(IO) }
+ end
+
+end
+
diff --git a/spec/yell/adapters_spec.rb b/spec/yell/adapters_spec.rb
new file mode 100644
index 0000000..bbb1495
--- /dev/null
+++ b/spec/yell/adapters_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe Yell::Adapters do
+
+ context ".new" do
+ it "should accept an adapter instance" do
+ stdout = Yell::Adapters::Stdout.new
+ adapter = Yell::Adapters.new(stdout)
+
+ expect(adapter).to eq(stdout)
+ end
+
+ it "should accept STDOUT" do
+ mock.proxy(Yell::Adapters::Stdout).new(anything)
+
+ Yell::Adapters.new(STDOUT)
+ end
+
+ it "should accept STDERR" do
+ mock.proxy(Yell::Adapters::Stderr).new(anything)
+
+ Yell::Adapters.new(STDERR)
+ end
+
+ it "should raise an unregistered adapter" do
+ expect {
+ Yell::Adapters.new :unknown
+ }.to raise_error(Yell::AdapterNotFound)
+ end
+ end
+
+ context ".register" do
+ let(:name) { :test }
+ let(:klass) { mock }
+
+ before { Yell::Adapters.register(name, klass) }
+
+ it "should allow to being called from :new" do
+ mock(klass).new(anything)
+
+ Yell::Adapters.new(name)
+ end
+ end
+
+end
diff --git a/spec/yell/configuration_spec.rb b/spec/yell/configuration_spec.rb
new file mode 100644
index 0000000..1010b89
--- /dev/null
+++ b/spec/yell/configuration_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe Yell::Configuration do
+
+ describe ".load!" do
+ let(:file) { fixture_path + '/yell.yml' }
+ let(:config) { Yell::Configuration.load!(file) }
+
+ subject { config }
+
+ it { should be_kind_of(Hash) }
+ it { should have_key(:level) }
+ it { should have_key(:adapters) }
+
+ context ":level" do
+ subject { config[:level] }
+
+ it { should eq("info") }
+ end
+
+ context ":adapters" do
+ subject { config[:adapters] }
+
+ it { should be_kind_of(Array) }
+
+ # stdout
+ it { expect(subject.first).to eq(:stdout) }
+
+ # stderr
+ it { expect(subject.last).to be_kind_of(Hash) }
+ it { expect(subject.last).to eq(:stderr => {:level => 'gte.error'}) }
+ end
+ end
+
+end
+
diff --git a/spec/yell/dsl_spec.rb b/spec/yell/dsl_spec.rb
new file mode 100644
index 0000000..44f3396
--- /dev/null
+++ b/spec/yell/dsl_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe "Yell Adapter DSL spec" do
+
+ class DSLAdapter < Yell::Adapters::Base
+
+ setup do |options|
+ @test_setup = true
+ end
+
+ write do |event|
+ @test_write = true
+ end
+
+ close do
+ @test_close = true
+ end
+
+ def test_setup?; !!@test_setup; end
+ def test_write?; !!@test_write; end
+ def test_close?; !!@test_close; end
+ end
+
+ it "should perform #setup" do
+ adapter = DSLAdapter.new
+ expect(adapter.test_setup?).to be_true
+ end
+
+ it "should perform #write" do
+ event = 'event'
+ stub(event).level { 0 }
+
+ adapter = DSLAdapter.new
+ expect(adapter.test_write?).to be_false
+
+ adapter.write(event)
+ expect(adapter.test_write?).to be_true
+ end
+
+ it "should perform #close" do
+ adapter = DSLAdapter.new
+ expect(adapter.test_close?).to be_false
+
+ adapter.close
+ expect(adapter.test_close?).to be_true
+ end
+
+end
+
diff --git a/spec/yell/event_spec.rb b/spec/yell/event_spec.rb
new file mode 100644
index 0000000..c4a322b
--- /dev/null
+++ b/spec/yell/event_spec.rb
@@ -0,0 +1,97 @@
+require 'spec_helper'
+
+# Since Yell::Event.new is not called directly, but through
+# the logger methods, we need to divert here in order to get
+# the correct caller.
+class EventFactory
+ def self.event(logger, level, message)
+ self._event(logger, level, message)
+ end
+
+ private
+
+ def self._event(logger, level, message)
+ Yell::Event.new(logger, level, message)
+ end
+
+end
+
+describe Yell::Event do
+ let(:logger) { Yell::Logger.new(:trace => true) }
+ let(:event) { Yell::Event.new(logger, 1, 'Hello World!') }
+
+ context "#level" do
+ subject { event.level }
+ it { should eq(1) }
+ end
+
+ context "#messages" do
+ subject { event.messages }
+ it { should eq(['Hello World!']) }
+ end
+
+ context "#time" do
+ let(:time) { Time.now }
+ subject { event.time.to_s }
+
+ before { Timecop.freeze(time) }
+
+ it { should eq(time.to_s) }
+ end
+
+ context "#hostname" do
+ subject { event.hostname }
+ it { should eq(Socket.gethostname) }
+ end
+
+ context "#pid" do
+ subject { event.pid }
+ it { should eq(Process.pid) }
+ end
+
+ context "#id when forked", :pending => RUBY_PLATFORM == 'java' ? "No forking with jruby" : false do
+ subject { @pid }
+
+ before do
+ read, write = IO.pipe
+
+ @pid = Process.fork do
+ event = Yell::Event.new(logger, 1, 'Hello World!')
+ write.puts event.pid
+ end
+ Process.wait
+ write.close
+
+ @child_pid = read.read.to_i
+ read.close
+ end
+
+ it { should_not eq(Process.pid) }
+ it { should eq(@child_pid) }
+ end
+
+ context "#progname" do
+ subject { event.progname }
+ it { should eq($0) }
+ end
+
+ context ":caller" do
+ subject { EventFactory.event(logger, 1, "Hello World") }
+
+ context "with trace" do
+ its(:file) { should eq(__FILE__) }
+ its(:line) { should eq("8") }
+ its(:method) { should eq("event") }
+ end
+
+ context "without trace" do
+ before { logger.trace = false }
+
+ its(:file) { should eq("") }
+ its(:line) { should eq("") }
+ its(:method) { should eq("") }
+ end
+ end
+
+end
+
diff --git a/spec/yell/formatter_spec.rb b/spec/yell/formatter_spec.rb
new file mode 100644
index 0000000..ffac567
--- /dev/null
+++ b/spec/yell/formatter_spec.rb
@@ -0,0 +1,146 @@
+require 'spec_helper'
+
+describe Yell::Formatter do
+
+ let(:logger) { Yell::Logger.new(:stdout, :name => 'Yell') }
+ let(:message) { "Hello World!" }
+ let(:event) { Yell::Event.new(logger, 1, message) }
+
+ let(:pattern) { "%m" }
+ let(:formatter) { Yell::Formatter.new(pattern) }
+
+ let(:time) { Time.now }
+
+ subject { formatter.call(event) }
+
+ before do
+ Timecop.freeze(time)
+ end
+
+ describe "patterns" do
+ context "%m" do
+ let(:pattern) { "%m" }
+ it { should eq("#{event.messages.join(' ')}\n") }
+ end
+
+ context "%l" do
+ let(:pattern) { "%l" }
+ it { should eq("#{Yell::Severities[event.level][0,1]}\n") }
+ end
+
+ context "%L" do
+ let(:pattern) { "%L" }
+ it { should eq("#{Yell::Severities[event.level]}\n") }
+ end
+
+ context "%d" do
+ let(:pattern) { "%d" }
+ it { should eq("#{event.time.iso8601}\n") }
+ end
+
+ context "%p" do
+ let(:pattern) { "%p" }
+ it { should eq("#{event.pid}\n") }
+ end
+
+ context "%P" do
+ let(:pattern) { "%P" }
+ it { should eq("#{event.progname}\n") }
+ end
+
+ context "%t" do
+ let(:pattern) { "%t" }
+ it { should eq("#{event.thread_id}\n") }
+ end
+
+ context "%h" do
+ let(:pattern) { "%h" }
+ it { should eq("#{event.hostname}\n") }
+ end
+
+ context ":caller" do
+ let(:_caller) { [nil, nil, "/path/to/file.rb:123:in `test_method'"] }
+
+ before do
+ any_instance_of(Yell::Event) do |e|
+ stub(e).file { "/path/to/file.rb" }
+ stub(e).line { "123" }
+ stub(e).method { "test_method" }
+ end
+ end
+
+ context "%F" do
+ let(:pattern) { "%F" }
+ it { should eq("/path/to/file.rb\n") }
+ end
+
+ context "%f" do
+ let(:pattern) { "%f" }
+ it { should eq("file.rb\n") }
+ end
+
+ context "%M" do
+ let(:pattern) { "%M" }
+ it { should eq("test_method\n") }
+ end
+
+ context "%n" do
+ let(:pattern) { "%n" }
+ it { should eq("123\n") }
+ end
+ end
+
+ context "%N" do
+ let(:pattern) { "%N" }
+ it { should eq("Yell\n") }
+ end
+ end
+
+ describe "presets" do
+ context "NoFormat" do
+ let(:pattern) { Yell::NoFormat }
+ it { should eq("Hello World!\n") }
+ end
+
+ context "DefaultFormat" do
+ let(:pattern) { Yell::DefaultFormat }
+ it { should eq("#{time.iso8601} [ INFO] #{$$} : Hello World!\n") }
+ end
+
+ context "BasicFormat" do
+ let(:pattern) { Yell::BasicFormat }
+ it { should eq("I, #{time.iso8601} : Hello World!\n") }
+ end
+
+ context "ExtendedFormat" do
+ let(:pattern) { Yell::ExtendedFormat }
+ it { should eq("#{time.iso8601} [ INFO] #{$$} #{Socket.gethostname} : Hello World!\n") }
+ end
+ end
+
+ describe "Exception" do
+ let(:message) { StandardError.new("This is an Exception") }
+
+ before do
+ stub(message).backtrace { ["backtrace"] }
+ end
+
+ it { should eq("StandardError: This is an Exception\n\tbacktrace\n") }
+ end
+
+ describe "Hash" do
+ let(:message) { {:test => 'message'} }
+
+ it { should eq("test: message\n") }
+ end
+
+ describe "custom message modifiers" do
+ let(:formatter) do
+ Yell::Formatter.new(pattern) { |f| f.modify(String) { |m| "Modified! #{m}" } }
+ end
+
+ it { should eq("Modified! #{message}\n") }
+ end
+
+end
+
diff --git a/spec/yell/level_spec.rb b/spec/yell/level_spec.rb
new file mode 100644
index 0000000..03f3a06
--- /dev/null
+++ b/spec/yell/level_spec.rb
@@ -0,0 +1,200 @@
+require 'spec_helper'
+
+describe Yell::Level do
+
+ context "default" do
+ let(:level) { Yell::Level.new }
+
+ it "should should return correctly" do
+ expect(level.at?(:debug)).to be_true
+ expect(level.at?(:info)).to be_true
+ expect(level.at?(:warn)).to be_true
+ expect(level.at?(:error)).to be_true
+ expect(level.at?(:fatal)).to be_true
+ end
+ end
+
+ context "given a Symbol" do
+ let(:level) { Yell::Level.new(severity) }
+
+ context ":debug" do
+ let(:severity) { :debug }
+
+ it "should should return correctly" do
+ expect(level.at?(:debug)).to be_true
+ expect(level.at?(:info)).to be_true
+ expect(level.at?(:warn)).to be_true
+ expect(level.at?(:error)).to be_true
+ expect(level.at?(:fatal)).to be_true
+ end
+ end
+
+ context ":info" do
+ let(:severity) { :info }
+
+ it "should should return correctly" do
+ expect(level.at?(:debug)).to be_false
+ expect(level.at?(:info)).to be_true
+ expect(level.at?(:warn)).to be_true
+ expect(level.at?(:error)).to be_true
+ expect(level.at?(:fatal)).to be_true
+ end
+ end
+
+ context ":warn" do
+ let(:severity) { :warn }
+
+ it "should should return correctly" do
+ expect(level.at?(:debug)).to be_false
+ expect(level.at?(:info)).to be_false
+ expect(level.at?(:warn)).to be_true
+ expect(level.at?(:error)).to be_true
+ expect(level.at?(:fatal)).to be_true
+ end
+ end
+
+ context ":error" do
+ let(:severity) { :error }
+
+ it "should should return correctly" do
+ expect(level.at?(:debug)).to be_false
+ expect(level.at?(:info)).to be_false
+ expect(level.at?(:warn)).to be_false
+ expect(level.at?(:error)).to be_true
+ expect(level.at?(:fatal)).to be_true
+ end
+ end
+
+ context ":fatal" do
+ let(:severity) { :fatal }
+
+ it "should should return correctly" do
+ expect(level.at?(:debug)).to be_false
+ expect(level.at?(:info)).to be_false
+ expect(level.at?(:warn)).to be_false
+ expect(level.at?(:error)).to be_false
+ expect(level.at?(:fatal)).to be_true
+ end
+ end
+ end
+
+ context "given a String" do
+ let(:level) { Yell::Level.new(severity) }
+
+ context "basic string" do
+ let(:severity) { 'error' }
+
+ it "should should return correctly" do
+ expect(level.at?(:debug)).to be_false
+ expect(level.at?(:info)).to be_false
+ expect(level.at?(:warn)).to be_false
+ expect(level.at?(:error)).to be_true
+ expect(level.at?(:fatal)).to be_true
+ end
+ end
+
+ context "complex string with outer boundaries" do
+ let(:severity) { 'gte.info lte.error' }
+
+ it "should should return correctly" do
+ expect(level.at?(:debug)).to be_false
+ expect(level.at?(:info)).to be_true
+ expect(level.at?(:warn)).to be_true
+ expect(level.at?(:error)).to be_true
+ expect(level.at?(:fatal)).to be_false
+ end
+ end
+
+ context "complex string with inner boundaries" do
+ let(:severity) { 'gt.info lt.error' }
+
+ it "should be valid" do
+ expect(level.at?(:debug)).to be_false
+ expect(level.at?(:info)).to be_false
+ expect(level.at?(:warn)).to be_true
+ expect(level.at?(:error)).to be_false
+ expect(level.at?(:fatal)).to be_false
+ end
+ end
+
+ context "complex string with precise boundaries" do
+ let(:severity) { 'at.info at.error' }
+
+ it "should be valid" do
+ expect(level.at?(:debug)).to be_false
+ expect(level.at?(:info)).to be_true
+ expect(level.at?(:warn)).to be_false
+ expect(level.at?(:error)).to be_true
+ expect(level.at?(:fatal)).to be_false
+ end
+ end
+
+ context "complex string with combined boundaries" do
+ let(:severity) { 'gte.error at.debug' }
+
+ it "should be valid" do
+ expect(level.at?(:debug)).to be_true
+ expect(level.at?(:info)).to be_false
+ expect(level.at?(:warn)).to be_false
+ expect(level.at?(:error)).to be_true
+ expect(level.at?(:fatal)).to be_true
+ end
+ end
+ end
+
+ context "given an Array" do
+ let(:level) { Yell::Level.new( [:debug, :warn, :fatal] ) }
+
+ it "should return correctly" do
+ expect(level.at?(:debug)).to be_true
+ expect(level.at?(:info)).to be_false
+ expect(level.at?(:warn)).to be_true
+ expect(level.at?(:error)).to be_false
+ expect(level.at?(:fatal)).to be_true
+ end
+ end
+
+ context "given a Range" do
+ let(:level) { Yell::Level.new( (1..3) ) }
+
+ it "should return correctly" do
+ expect(level.at?(:debug)).to be_false
+ expect(level.at?(:info)).to be_true
+ expect(level.at?(:warn)).to be_true
+ expect(level.at?(:error)).to be_true
+ expect(level.at?(:fatal)).to be_false
+ end
+ end
+
+ context "given a Yell::Level instance" do
+ let(:level) { Yell::Level.new(:warn) }
+
+ it "should return correctly" do
+ expect(level.at?(:debug)).to be_false
+ expect(level.at?(:info)).to be_false
+ expect(level.at?(:warn)).to be_true
+ expect(level.at?(:error)).to be_true
+ expect(level.at?(:fatal)).to be_true
+ end
+ end
+
+ context "backwards compatibility" do
+ let(:level) { Yell::Level.new :warn }
+
+ it "should return correctly to :to_i" do
+ expect(level.to_i).to eq(2)
+ end
+
+ it "should typecast with Integer correctly" do
+ expect(Integer(level)).to eq(2)
+ end
+
+ it "should be compatible when passing to array (https://github.com/rudionrails/yell/issues/1)" do
+ severities = %w(FINE INFO WARNING SEVERE SEVERE INFO)
+
+ expect(severities[level]).to eq("WARNING")
+ end
+ end
+
+end
+
diff --git a/spec/yell/loggable_spec.rb b/spec/yell/loggable_spec.rb
new file mode 100644
index 0000000..2c56dd4
--- /dev/null
+++ b/spec/yell/loggable_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+class LoggableFactory
+ include Yell::Loggable
+end
+
+describe Yell::Loggable do
+ let(:factory) { LoggableFactory.new }
+ subject { factory }
+
+ it { should respond_to(:logger) }
+
+ it "should make a lookup in the Yell::Repository" do
+ mock(Yell::Repository)[LoggableFactory]
+
+ factory.logger
+ end
+
+end
+
diff --git a/spec/yell/logger_spec.rb b/spec/yell/logger_spec.rb
new file mode 100644
index 0000000..8be5db9
--- /dev/null
+++ b/spec/yell/logger_spec.rb
@@ -0,0 +1,268 @@
+require 'spec_helper'
+
+class LoggerFactory
+ attr_accessor :logger
+
+ def info
+ logger.info :foo
+ end
+
+ def add
+ logger.add 1, :bar
+ end
+end
+
+describe Yell::Logger do
+ let(:filename) { fixture_path + '/logger.log' }
+
+ describe "a Logger instance" do
+ let(:logger) { Yell::Logger.new }
+ subject { logger }
+
+ context "log methods" do
+ it { should respond_to(:debug) }
+ it { should respond_to(:debug?) }
+
+ it { should respond_to(:info) }
+ it { should respond_to(:info?) }
+
+ it { should respond_to(:warn) }
+ it { should respond_to(:warn?) }
+
+ it { should respond_to(:error) }
+ it { should respond_to(:error?) }
+
+ it { should respond_to(:fatal) }
+ it { should respond_to(:fatal?) }
+
+ it { should respond_to(:unknown) }
+ it { should respond_to(:unknown?) }
+ end
+
+ context "default #name" do
+ its(:name) { should eq("<Yell::Logger##{logger.object_id}>") }
+
+ it "should not be added to the repository" do
+ expect { Yell::Repository[logger.name] }.to raise_error(Yell::LoggerNotFound)
+ end
+ end
+
+ context "default #adapter" do
+ subject { logger.adapters.instance_variable_get(:@collection) }
+
+ its(:size) { should == 1 }
+ its(:first) { should be_kind_of(Yell::Adapters::File) }
+ end
+
+ context "default #level" do
+ subject { logger.level }
+
+ it { should be_instance_of(Yell::Level) }
+ its(:severities) { should eq([true, true, true, true, true, true]) }
+ end
+
+ context "default #trace" do
+ subject { logger.trace }
+
+ it { should be_instance_of(Yell::Level) }
+ its(:severities) { should eq([false, false, false, true, true, true]) } # from error onwards
+ end
+ end
+
+ describe "initialize with #name" do
+ let(:name) { 'test' }
+ let!(:logger) { Yell.new(:name => name) }
+
+ it "should set the name correctly" do
+ expect(logger.name).to eq(name)
+ end
+
+ it "should be added to the repository" do
+ expect(Yell::Repository[name]).to eq(logger)
+ end
+ end
+
+ context "initialize with #level" do
+ let(:level) { :error }
+ let(:logger) { Yell.new(:level => level) }
+ subject { logger.level }
+
+ it { should be_instance_of(Yell::Level) }
+ its(:severities) { should eq([false, false, false, true, true, true]) }
+ end
+
+ context "initialize with #trace" do
+ let(:trace) { :info }
+ let(:logger) { Yell.new(:trace => trace) }
+ subject { logger.trace }
+
+ it { should be_instance_of(Yell::Level) }
+ its(:severities) { should eq([false, true, true, true, true, true]) }
+ end
+
+ context "initialize with #silence" do
+ let(:silence) { "test" }
+ let(:logger) { Yell.new(:silence => silence) }
+ subject { logger.silencer }
+
+ it { should be_instance_of(Yell::Silencer) }
+ its(:patterns) { should eq([silence]) }
+ end
+
+ context "initialize with a #filename" do
+ it "should call adapter with :file" do
+ mock.proxy(Yell::Adapters::File).new(:filename => filename)
+
+ Yell::Logger.new(filename)
+ end
+ end
+
+ context "initialize with a #filename of Pathname type" do
+ let(:pathname) { Pathname.new(filename) }
+
+ it "should call adapter with :file" do
+ mock.proxy(Yell::Adapters::File).new(:filename => pathname)
+
+ Yell::Logger.new(pathname)
+ end
+ end
+
+ context "initialize with a :stdout adapter" do
+ before { mock.proxy(Yell::Adapters::Stdout).new(anything) }
+
+ it "should call adapter with STDOUT" do
+ Yell::Logger.new(STDOUT)
+ end
+
+ it "should call adapter with :stdout" do
+ Yell::Logger.new(:stdout)
+ end
+ end
+
+ context "initialize with a :stderr adapter" do
+ before { mock.proxy(Yell::Adapters::Stderr).new(anything) }
+
+ it "should call adapter with STDERR" do
+ Yell::Logger.new(STDERR)
+ end
+
+ it "should call adapter with :stderr" do
+ Yell::Logger.new(:stderr)
+ end
+ end
+
+ context "initialize with a block" do
+ let(:level) { Yell::Level.new :error }
+ let(:adapters) { logger.adapters.instance_variable_get(:@collection) }
+
+ context "with arity" do
+ let(:logger) do
+ Yell::Logger.new(:level => level) { |l| l.adapter(:stdout) }
+ end
+
+ it "should pass the level correctly" do
+ expect(logger.level).to eq(level)
+ end
+
+ it "should pass the adapter correctly" do
+ expect(adapters.first).to be_instance_of(Yell::Adapters::Stdout)
+ end
+ end
+
+ context "without arity" do
+ let(:logger) do
+ Yell::Logger.new(:level => level) { adapter(:stdout) }
+ end
+
+ it "should pass the level correctly" do
+ expect(logger.level).to eq(level)
+ end
+
+ it "should pass the adapter correctly" do
+ expect(adapters.first).to be_instance_of(Yell::Adapters::Stdout)
+ end
+ end
+ end
+
+ context "initialize with #adapters option" do
+ it "should set adapters in logger correctly" do
+ any_instance_of(Yell::Logger) do |logger|
+ mock.proxy(logger).adapter(:stdout)
+ mock.proxy(logger).adapter(:stderr, :level => :error)
+ end
+
+ Yell::Logger.new(:adapters => [:stdout, {:stderr => {:level => :error}}])
+ end
+ end
+
+ context "caller's :file, :line and :method" do
+ let(:stdout) { Yell::Adapters::Stdout.new(:format => "%F, %n: %M") }
+ let(:logger) { Yell::Logger.new(:trace => true) { |l| l.adapter(stdout) } }
+
+ it "should write correctly" do
+ factory = LoggerFactory.new
+ factory.logger = logger
+
+ mock(stdout.send(:stream)).syswrite("#{__FILE__}, 7: info\n")
+ mock(stdout.send(:stream)).syswrite("#{__FILE__}, 11: add\n")
+
+ factory.info
+ factory.add
+ end
+ end
+
+ context "logging in general" do
+ let(:logger) { Yell::Logger.new(filename, :format => "%m") }
+ let(:line) { File.open(filename, &:readline) }
+
+ it "should output a single message" do
+ logger.info "Hello World"
+
+ expect(line).to eq("Hello World\n")
+ end
+
+ it "should output multiple messages" do
+ logger.info ["Hello", "W", "o", "r", "l", "d"]
+
+ expect(line).to eq("Hello W o r l d\n")
+ end
+
+ it "should output a hash and message" do
+ logger.info ["Hello World", {:test => :message}]
+
+ expect(line).to eq("Hello World test: message\n")
+ end
+
+ it "should output a hash and message" do
+ logger.info [{:test => :message}, "Hello World"]
+
+ expect(line).to eq("test: message Hello World\n")
+ end
+
+ it "should output a hash and block" do
+ logger.info(:test => :message) { "Hello World" }
+
+ expect(line).to eq("test: message Hello World\n")
+ end
+ end
+
+ context "logging with a silencer" do
+ let(:silence) { "this" }
+ let(:stdout) { Yell::Adapters::Stdout.new }
+ let(:logger) { Yell::Logger.new(stdout, :silence => silence) }
+
+ it "should not pass a matching message to any adapter" do
+ dont_allow(stdout).write
+
+ logger.info "this should not be logged"
+ end
+
+ it "should pass a non-matching message to any adapter" do
+ mock(stdout).write(is_a(Yell::Event))
+
+ logger.info "that should be logged"
+ end
+ end
+
+end
+
diff --git a/spec/yell/repository_spec.rb b/spec/yell/repository_spec.rb
new file mode 100644
index 0000000..818524b
--- /dev/null
+++ b/spec/yell/repository_spec.rb
@@ -0,0 +1,70 @@
+require 'spec_helper'
+
+describe Yell::Repository do
+ let(:name) { 'test' }
+ let(:logger) { Yell.new(:stdout) }
+
+ subject { Yell::Repository[name] }
+
+ context ".[]" do
+ it "should raise when not set" do
+ expect { subject }.to raise_error(Yell::LoggerNotFound)
+ end
+
+ context "when logger with :name exists" do
+ let!(:logger) { Yell.new(:stdout, :name => name) }
+
+ it { should eq(logger) }
+ end
+
+ context "given a Class" do
+ let!(:logger) { Yell.new(:stdout, :name => "Numeric") }
+
+ it "should raise with the correct :name when logger not found" do
+ mock.proxy(Yell::LoggerNotFound).new(String)
+ lambda { Yell::Repository[String] }.should raise_error(Yell::LoggerNotFound)
+ end
+
+ it "should return the logger" do
+ Yell::Repository[Numeric].should eq(logger)
+ end
+
+ it "should return the logger when superclass has it defined" do
+ Yell::Repository[Integer].should eq(logger)
+ end
+ end
+ end
+
+ context ".[]=" do
+ before { Yell::Repository[name] = logger }
+ it { should eq(logger) }
+ end
+
+ context ".[]= with a named logger" do
+ let!(:logger) { Yell.new(:stdout, :name => name) }
+ before { Yell::Repository[name] = logger }
+
+ it { should eq(logger) }
+ end
+
+ context ".[]= with a named logger of a different name" do
+ let(:other) { 'other' }
+ let(:logger) { Yell.new(:stdout, :name => other) }
+ before { Yell::Repository[name] = logger }
+
+ it "should add logger to both repositories" do
+ Yell::Repository[name].should eq(logger)
+ Yell::Repository[other].should eq(logger)
+ end
+ end
+
+ context "loggers" do
+ let(:loggers) { { name => logger } }
+ subject { Yell::Repository.loggers }
+ before { Yell::Repository[name] = logger }
+
+ it { should eq(loggers) }
+ end
+
+end
+
diff --git a/spec/yell/silencer_spec.rb b/spec/yell/silencer_spec.rb
new file mode 100644
index 0000000..5d81e9e
--- /dev/null
+++ b/spec/yell/silencer_spec.rb
@@ -0,0 +1,38 @@
+require 'spec_helper'
+
+describe Yell::Silencer do
+
+ context "initialize with #patterns" do
+ subject { Yell::Silencer.new(/this/) }
+
+ its(:patterns) { should eq([/this/]) }
+ end
+
+ context "#add" do
+ let(:silencer) { Yell::Silencer.new }
+
+ it "should add patterns" do
+ silencer.add /this/, /that/
+
+ expect(silencer.patterns).to eq([/this/, /that/])
+ end
+
+ it "should ignore duplicate patterns" do
+ silencer.add /this/, /that/, /this/
+
+ expect(silencer.patterns).to eq([/this/, /that/])
+ end
+ end
+
+ context "#call" do
+ let(:silencer) { Yell::Silencer.new(/this/) }
+
+ it "should reject messages that match any pattern" do
+ expect(silencer.call("this")).to eq([])
+ expect(silencer.call("that")).to eq(["that"])
+ expect(silencer.call("this", "that")).to eq(["that"])
+ end
+ end
+
+end
+
diff --git a/spec/yell_spec.rb b/spec/yell_spec.rb
new file mode 100644
index 0000000..370ebf3
--- /dev/null
+++ b/spec/yell_spec.rb
@@ -0,0 +1,102 @@
+require 'spec_helper'
+
+describe Yell do
+ let( :logger ) { Yell.new }
+
+ subject { logger }
+
+ it { should be_kind_of Yell::Logger }
+
+ it "should raise AdapterNotFound when adapter cant be loaded" do
+ expect {
+ Yell.new :unknownadapter
+ }.to raise_error(Yell::AdapterNotFound)
+ end
+
+ context ".level" do
+ subject { Yell.level }
+ it { should be_kind_of Yell::Level }
+ end
+
+ context ".format" do
+ subject { Yell.format( "%m" ) }
+ it { should be_kind_of Yell::Formatter }
+ end
+
+ context ".load!" do
+ subject { Yell.load!('yell.yml') }
+
+ before do
+ mock(Yell::Configuration).load!('yell.yml') { {} }
+ end
+
+ it { should be_kind_of Yell::Logger }
+ end
+
+ context ".[]" do
+ let(:name) { 'test' }
+
+ it "should delegate to the repository" do
+ mock(Yell::Repository)[name]
+
+ Yell[name]
+ end
+ end
+
+ context ".[]=" do
+ let(:name) { 'test' }
+
+ it "should delegate to the repository" do
+ mock.proxy(Yell::Repository)[name] = logger
+
+ Yell[name] = logger
+ end
+ end
+
+ context ".env" do
+ subject { Yell.env }
+
+ it "should default to YELL_ENV" do
+ subject.should == 'test'
+ end
+
+ context "fallback to RACK_ENV" do
+ before do
+ stub(ENV).key?('YELL_ENV') { false }
+ mock(ENV).key?('RACK_ENV') { true }
+
+ ENV['RACK_ENV'] = 'rack'
+ end
+
+ after { ENV.delete 'RACK_ENV' }
+
+ it { should == 'rack' }
+ end
+
+ context "fallback to RAILS_ENV" do
+ before do
+ stub(ENV).key?('YELL_ENV') { false }
+ stub(ENV).key?('RACK_ENV') { false }
+ mock(ENV).key?('RAILS_ENV') { true }
+
+ ENV['RAILS_ENV'] = 'rails'
+ end
+
+ after { ENV.delete 'RAILS_ENV' }
+
+ it { should == 'rails' }
+ end
+
+ context "fallback to development" do
+ before do
+ stub(ENV).key?('YELL_ENV') { false }
+ stub(ENV).key?('RACK_ENV') { false }
+ stub(ENV).key?('RAILS_ENV') { false }
+ end
+
+ it { should == 'development' }
+ end
+ end
+
+end
+
diff --git a/yell.gemspec b/yell.gemspec
new file mode 100644
index 0000000..1624bd9
--- /dev/null
+++ b/yell.gemspec
@@ -0,0 +1,23 @@
+# coding: utf-8
+lib = File.expand_path('../lib', __FILE__)
+$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
+require 'yell/version'
+
+Gem::Specification.new do |spec|
+ spec.name = "yell"
+ spec.version = Yell::VERSION
+ spec.authors = ["Rudolf Schmidt"]
+ spec.license = 'MIT'
+
+ spec.homepage = "http://rudionrailspec.github.com/yell"
+ spec.summary = %q{Yell - Your Extensible Logging Library}
+ spec.description = %q{Yell - Your Extensible Logging Library. Define multiple adapters, various log level combinations or message formatting options like you've never done before}
+
+ spec.rubyforge_project = "yell"
+
+ spec.files = `git ls-files -z`.split("\x0")
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
+ spec.require_paths = ["lib"]
+end
+
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-yell.git
More information about the Pkg-ruby-extras-commits
mailing list