This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
pkg/*
|
||||
*.gem
|
||||
.bundle
|
||||
.idea
|
||||
.vscode
|
||||
vendor/
|
||||
# bundler
|
||||
Gemfile.*lock
|
||||
|
||||
# vim
|
||||
*.swp
|
||||
|
||||
# coverage
|
||||
/coverage
|
||||
|
||||
# rspec
|
||||
/spec/examples/txt
|
||||
@@ -0,0 +1,20 @@
|
||||
language: ruby
|
||||
before_install: gem install bundler
|
||||
script: "bundle exec rspec"
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- rvm: ruby-head
|
||||
- rvm: 2.6
|
||||
- rvm: 2.5
|
||||
- rvm: 2.4
|
||||
- rvm: 2.3
|
||||
- rvm: jruby-9.1
|
||||
- rvm: jruby-9.2
|
||||
allow_failures:
|
||||
- rvm: ruby-head
|
||||
- rvm: jruby-head
|
||||
|
||||
notifications:
|
||||
on_success: change
|
||||
on_failure: change
|
||||
@@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
source 'http://rubygems.org'
|
||||
|
||||
# Specify your gem's dependencies in yell.gemspec
|
||||
gemspec
|
||||
|
||||
group :development, :test do
|
||||
gem 'rake'
|
||||
|
||||
gem 'rspec-core', '~> 3'
|
||||
gem 'rspec-expectations'
|
||||
gem 'rspec-its'
|
||||
gem 'rspec-mocks'
|
||||
|
||||
gem 'byebug', platform: :mri
|
||||
gem 'timecop'
|
||||
|
||||
gem 'activesupport', '~> 5'
|
||||
|
||||
gem 'coveralls', require: false
|
||||
gem 'rubocop', require: false
|
||||
gem 'simplecov', require: false
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
Copyright (c) 2011-current 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.
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
# 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 its test suite currently runs on:
|
||||
|
||||
- ruby-head, 2.3.1, 2.2.5, 2.2.2, 2.1.0, 2.0.0
|
||||
- jruby-head, jruby-9.1.0.0, jruby-9.0.0.0
|
||||
|
||||
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)
|
||||
[syslog-sd](https://github.com/raymond-wells/yell-adapters-syslogsd)
|
||||
[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-current Rudolf Schmidt, released under the MIT license
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!"
|
||||
|
||||
@@ -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"
|
||||
@@ -0,0 +1,29 @@
|
||||
# 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:
|
||||
# * %i[] is a built-in for an array of symbols
|
||||
|
||||
logger = Yell.new STDOUT, level: %i[debug warn]
|
||||
|
||||
%i[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: %i[debug warn]
|
||||
|
||||
%i[debug info warn error fatal].each do |level|
|
||||
logger.send( level, level )
|
||||
end
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# 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:
|
||||
# * %i[] is a built-in for an array of symbols
|
||||
|
||||
|
||||
logger = Yell.new STDOUT, level: (:debug..:warn)
|
||||
|
||||
%i[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 "=== actual example ==="
|
||||
logger = Yell.new STDOUT, level: (:debug..:warn)
|
||||
|
||||
%i[debug info warn error fatal].each do |level|
|
||||
logger.send( level, level )
|
||||
end
|
||||
|
||||
@@ -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 "=== actual example ==="
|
||||
logger = Yell.new STDOUT, format: Yell::DefaultFormat
|
||||
logger.info "Hello World!"
|
||||
|
||||
@@ -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 "=== actual example ==="
|
||||
logger = Yell.new STDOUT, format: Yell::BasicFormat
|
||||
logger.info "Hello World!"
|
||||
|
||||
@@ -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 "=== actual example ==="
|
||||
logger = Yell.new STDOUT, format: Yell::ExtendedFormat
|
||||
logger.info "Hello World!"
|
||||
|
||||
@@ -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 "=== actual example ==="
|
||||
logger = Yell.new STDOUT, format: "[%f:%n in `%M'] %m", trace: true
|
||||
logger.info "Hello World!"
|
||||
|
||||
@@ -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 "=== actual example ==="
|
||||
logger = Yell.new STDOUT, colors: true
|
||||
|
||||
Yell::Severities.each do |level|
|
||||
logger.send level.downcase, level
|
||||
end
|
||||
|
||||
@@ -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 "=== actual example ==="
|
||||
Yell.new :stdout, name: 'mylog'
|
||||
Yell['mylog'].info "Hello World!"
|
||||
|
||||
@@ -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!"
|
||||
|
||||
@@ -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!"
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
require 'logger'
|
||||
|
||||
class Logger
|
||||
|
||||
def level_with_yell=( level )
|
||||
self.level_without_yell= level.is_a?(Yell::Level) ? Integer(level) : 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
|
||||
@@ -0,0 +1,144 @@
|
||||
# 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
|
||||
@@ -0,0 +1,84 @@
|
||||
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( type, options = {}, &block )
|
||||
return type if type.is_a?(Yell::Adapters::Base)
|
||||
|
||||
adapter = case type
|
||||
when STDOUT then @adapters[:stdout]
|
||||
when STDERR then @adapters[:stderr]
|
||||
else @adapters[type.to_sym]
|
||||
end
|
||||
|
||||
raise AdapterNotFound.new(type) 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'
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
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', 'Hello 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
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
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 %i[date_pattern header keep symlink]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
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
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
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
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
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
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
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
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
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
|
||||
include Comparable
|
||||
|
||||
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
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
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.dup
|
||||
|
||||
@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
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
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
|
||||
@@ -0,0 +1,18 @@
|
||||
module Yell #:nodoc:
|
||||
module Helpers #:nodoc:
|
||||
module Base #:nodoc:
|
||||
|
||||
private
|
||||
|
||||
# stub
|
||||
def reset!
|
||||
end
|
||||
|
||||
def inspectables
|
||||
[]
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
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
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
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
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
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
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
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
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
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 )
|
||||
@tainted = false
|
||||
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
|
||||
|
||||
@tainted = true 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
|
||||
@@ -0,0 +1,31 @@
|
||||
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
|
||||
@@ -0,0 +1,160 @@
|
||||
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!( *list )
|
||||
list.each do |a|
|
||||
if a.is_a?(Hash)
|
||||
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
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
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
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
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
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
module Yell #:nodoc:
|
||||
VERSION = "2.2.2"
|
||||
end
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
lib = File.expand_path('lib', __dir__)
|
||||
$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.summary = 'Yell - Your Extensible Logging Library'
|
||||
spec.description = "Yell - Your Extensible Logging Library. Define multiple adapters, various log level combinations or message formatting options like you've never done before"
|
||||
spec.homepage = 'https://github.com/rudionrails/yell'
|
||||
|
||||
spec.metadata['homepage_uri'] = spec.homepage
|
||||
spec.metadata['source_code_uri'] = 'https://github.com/rudionrails/yell'
|
||||
|
||||
# Specify which files should be added to the gem when it is released.
|
||||
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
||||
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
||||
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
||||
end
|
||||
spec.bindir = 'exe'
|
||||
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
||||
spec.require_paths = ['lib']
|
||||
end
|
||||
Reference in New Issue
Block a user