Add bin and edit workflow
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 9s

This commit is contained in:
2026-09-16 13:11:16 -06:00
parent c8ac4fcae5
commit 4cee170d66
17576 changed files with 895740 additions and 2 deletions
+17
View File
@@ -0,0 +1,17 @@
pkg/*
*.gem
.bundle
.idea
.vscode
vendor/
# bundler
Gemfile.*lock
# vim
*.swp
# coverage
/coverage
# rspec
/spec/examples/txt
+20
View File
@@ -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
+24
View File
@@ -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
+21
View File
@@ -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.
+223
View File
@@ -0,0 +1,223 @@
# Yell [![Gem Version](https://badge.fury.io/rb/yell.svg)](http://badge.fury.io/rb/yell) [![Build Status](https://travis-ci.org/rudionrails/yell.svg?branch=master)](https://travis-ci.org/rudionrails/yell) [![Code Climate](https://codeclimate.com/github/rudionrails/yell.svg)](https://codeclimate.com/github/rudionrails/yell) [![Coverage Status](https://coveralls.io/repos/rudionrails/yell/badge.svg?branch=master)](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 &copy; 2011-current Rudolf Schmidt, released under the MIT license
+14
View File
@@ -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
+144
View File
@@ -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
+84
View File
@@ -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
+130
View File
@@ -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
+241
View File
@@ -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
+213
View File
@@ -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
+31
View File
@@ -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
+160
View File
@@ -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
+85
View File
@@ -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
+4
View File
@@ -0,0 +1,4 @@
module Yell #:nodoc:
VERSION = "2.2.2"
end
+28
View File
@@ -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