loggr 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/LICENSES.md +15 -0
- data/MIT-LICENSE +20 -0
- data/README.md +230 -0
- data/Rakefile +14 -0
- data/lib/logback-classic-0.9.29.jar +0 -0
- data/lib/logback-core-0.9.29.jar +0 -0
- data/lib/loggr.rb +38 -0
- data/lib/loggr/adapter.rb +114 -0
- data/lib/loggr/adapter/abstract.rb +25 -0
- data/lib/loggr/adapter/base.rb +54 -0
- data/lib/loggr/adapter/buffered.rb +25 -0
- data/lib/loggr/adapter/nop.rb +39 -0
- data/lib/loggr/adapter/rails.rb +36 -0
- data/lib/loggr/adapter/slf4j.rb +30 -0
- data/lib/loggr/factory.rb +9 -0
- data/lib/loggr/lint.rb +54 -0
- data/lib/loggr/severity.rb +16 -0
- data/lib/loggr/slf4j.rb +7 -0
- data/lib/loggr/slf4j/jars.rb +44 -0
- data/lib/loggr/slf4j/logger.rb +88 -0
- data/lib/loggr/slf4j/mdc.rb +35 -0
- data/lib/loggr/version.rb +3 -0
- data/lib/slf4j-api-1.6.1.jar +0 -0
- data/loggr.gemspec +26 -0
- data/test/logback_helper.rb +54 -0
- data/test/test_helper.rb +50 -0
- data/test/unit/adapter/abstract_test.rb +12 -0
- data/test/unit/adapter/base_test.rb +65 -0
- data/test/unit/adapter/buffered_test.rb +44 -0
- data/test/unit/adapter/nop_test.rb +24 -0
- data/test/unit/adapter/rails_test.rb +38 -0
- data/test/unit/adapter/slf4j_test.rb +25 -0
- data/test/unit/factory_test.rb +60 -0
- data/test/unit/slf4j/jars_test.rb +22 -0
- data/test/unit/slf4j/logger_test.rb +139 -0
- data/test/unit/slf4j/mdc_test.rb +77 -0
- metadata +151 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
module Loggr
|
2
|
+
module Adapter
|
3
|
+
|
4
|
+
# A basically abstract base class for logger backend
|
5
|
+
# implementations, provides a default implementation for MDC (hash & thread local based).
|
6
|
+
#
|
7
|
+
# Ensure to implement `#logger`.
|
8
|
+
#
|
9
|
+
class AbstractAdapter
|
10
|
+
|
11
|
+
# Implement which creates a new instance of a logger.
|
12
|
+
def logger(name, options = {})
|
13
|
+
raise "#{self.class.name}#logger is declared `abstract': implement #logger method"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Use a simple thread local hash as fake MDC, because it's
|
17
|
+
# not supported by the logger anyway - but it should be available
|
18
|
+
# for consistency and usage.
|
19
|
+
def mdc
|
20
|
+
mdc_key = "#{self.class.name}.mdc"
|
21
|
+
Thread.current[mdc_key] ||= Hash.new
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'loggr/adapter/abstract'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module Loggr
|
5
|
+
module Adapter
|
6
|
+
|
7
|
+
# Default backend which is backed Rubys Stdlib Logger.
|
8
|
+
#
|
9
|
+
class BaseAdapter < AbstractAdapter
|
10
|
+
|
11
|
+
#
|
12
|
+
#
|
13
|
+
def logger(name, options = {})
|
14
|
+
name = normalize_name(name)
|
15
|
+
@loggers ||= {}
|
16
|
+
@loggers[name] ||= build_new_logger(name, options)
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
# Constructs a new logger instance for the supplied options, is called
|
21
|
+
# by `#loggers` when no logger with this name already exists - instead of
|
22
|
+
# creating a new logger...
|
23
|
+
#
|
24
|
+
def build_new_logger(name, options = {})
|
25
|
+
::Logger.new(options[:to] || "#{name.to_s.gsub(/[:\s\/]+/, '_')}.log").tap do |logger|
|
26
|
+
logger.level = options[:level] || Logger::INFO
|
27
|
+
logger.progname = name
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Because we should also allow using class names, or objects
|
32
|
+
# to construct new loggers (like in SLF4J), it makes sense to
|
33
|
+
# have a method which normalizes some input, be it a string or
|
34
|
+
# whatnot to convert to an appropriate name.
|
35
|
+
#
|
36
|
+
# Subclasses are of course allowed to override this, of course
|
37
|
+
# loggers themself are allowed to tweak that name further - this
|
38
|
+
# is basically to enable creating loggers from classes/objects
|
39
|
+
# for AS::BufferedLoggers and Stdlib Loggers.
|
40
|
+
#
|
41
|
+
def normalize_name(name)
|
42
|
+
return name.to_str if name.respond_to?(:to_str)
|
43
|
+
case name
|
44
|
+
when Symbol then name.to_s
|
45
|
+
when Module then name.name.to_s
|
46
|
+
else name.class.name.to_s
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Okay, basically a singleton thus create instance
|
52
|
+
Base = BaseAdapter.new
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'loggr/adapter/base'
|
2
|
+
require 'active_support/buffered_logger'
|
3
|
+
|
4
|
+
module Loggr
|
5
|
+
module Adapter
|
6
|
+
|
7
|
+
# Backend for `ActiveSupport::BufferedLogger`.
|
8
|
+
#
|
9
|
+
class BufferedAdapter < BaseAdapter
|
10
|
+
|
11
|
+
protected
|
12
|
+
# Creates a new `AS::BufferedLogger` instance, note that BufferedLogger has
|
13
|
+
# no support for setting a default progname, so `name` is basically ignored.
|
14
|
+
#
|
15
|
+
def build_new_logger(name, options = {})
|
16
|
+
ActiveSupport::BufferedLogger.new(options[:to] || "#{name.to_s.gsub(/[\s\/]+/, '_')}.log").tap do |logger|
|
17
|
+
logger.level = options[:level] || ActiveSupport::BufferedLogger::INFO
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# THE instance
|
23
|
+
Buffered = BufferedAdapter.new
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'loggr/adapter/abstract'
|
2
|
+
|
3
|
+
module Loggr
|
4
|
+
module Adapter
|
5
|
+
|
6
|
+
# Silences all logging operations, nothing is written at all.
|
7
|
+
#
|
8
|
+
class NOPAdapter < AbstractAdapter
|
9
|
+
|
10
|
+
class NOPLogger
|
11
|
+
# Has no impact anyway :)
|
12
|
+
attr_accessor :level
|
13
|
+
|
14
|
+
# Just to ensure compatiability with AS::BufferedLogger
|
15
|
+
attr_reader :auto_flushing, :flush, :close
|
16
|
+
|
17
|
+
# Yields empty implementations for all severities
|
18
|
+
%w{trace debug info warn error fatal}.each do |severity|
|
19
|
+
class_eval <<-EOT, __FILE__, __LINE__ + 1
|
20
|
+
def #{severity}(*args, &block) # def debug(*args, &block)
|
21
|
+
end # end
|
22
|
+
|
23
|
+
def #{severity}? # def debug?
|
24
|
+
false # false
|
25
|
+
end # end
|
26
|
+
EOT
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Get single NOPLogger instance
|
31
|
+
def logger(name, options = {})
|
32
|
+
@logger ||= NOPLogger.new
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# THE instance
|
37
|
+
NOP = NOPAdapter.new
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'loggr/adapter/abstract'
|
2
|
+
|
3
|
+
module Loggr
|
4
|
+
module Adapter
|
5
|
+
|
6
|
+
# Always uses the logger defined on `Rails.logger`, CAVEAT ensure to never
|
7
|
+
# ever set `Rails.logger` to this backend, i.e.:
|
8
|
+
#
|
9
|
+
# # IF YOU DID THIS...
|
10
|
+
# Loggr.adapter = Loggr::Adapter::Rails
|
11
|
+
#
|
12
|
+
# MyApp::Application.configure do
|
13
|
+
# # ...NEVER DO THIS !!!
|
14
|
+
# config.logger = Loggr.logger('rails')
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# If you are using the rails adapter, ensure that you do not override `config.logger`
|
18
|
+
# with an instance of the Rails adapter logger factory. Keep the default logger, or
|
19
|
+
# create a new one using:
|
20
|
+
#
|
21
|
+
# config.logger = Loggr.logger('rails', :backend => Loggr::Adapter::Buffered)
|
22
|
+
#
|
23
|
+
class RailsAdapter < AbstractAdapter
|
24
|
+
|
25
|
+
# The rails backend ignores all options as it just returns
|
26
|
+
# always returns `Rails.logger` :)
|
27
|
+
#
|
28
|
+
def logger(name, options = {})
|
29
|
+
::Rails.logger
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# THE Rails backed implementation instance
|
34
|
+
Rails = RailsAdapter.new
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'loggr/slf4j'
|
2
|
+
require 'loggr/adapter/base'
|
3
|
+
|
4
|
+
module Loggr
|
5
|
+
module Adapter
|
6
|
+
|
7
|
+
# Provides an adapter for the SLF4J Logger.
|
8
|
+
#
|
9
|
+
class SLF4JAdapter < BaseAdapter
|
10
|
+
# Use the SLF4J backed real MDC.
|
11
|
+
def mdc
|
12
|
+
@mdc ||= Loggr::SLF4J::MDC
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
# Create a new SLF4JLogger instance.
|
17
|
+
def build_new_logger(name, options = {})
|
18
|
+
Loggr::SLF4J::Logger.new(name, options)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Uses Logger#in_java_notation on name
|
22
|
+
def normalize_name(name)
|
23
|
+
Loggr::SLF4J::Logger.in_java_notation(name)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# THE instance of it
|
28
|
+
SLF4J = SLF4JAdapter.new
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'loggr/adapter'
|
2
|
+
|
3
|
+
# This is basically a factory facade to create new Logger instances
|
4
|
+
# which behave all like standard Ruby Stdlib Loggers.
|
5
|
+
#
|
6
|
+
class LoggerFactory
|
7
|
+
# Ensure we get all those factory methods directly here
|
8
|
+
extend Loggr::Adapter
|
9
|
+
end
|
data/lib/loggr/lint.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module Loggr
|
4
|
+
module Lint
|
5
|
+
|
6
|
+
# == Adapter and Logger Lint Tests
|
7
|
+
#
|
8
|
+
# You can test whether an object provides a compliant adapter and logger
|
9
|
+
# by including <tt>Logger::Lint::Tests</tt>
|
10
|
+
# in your tests.
|
11
|
+
#
|
12
|
+
# Ensure you set the instance variable <tt>@adapter</tt> to your adapter.
|
13
|
+
#
|
14
|
+
module Tests
|
15
|
+
def test_adapter_logger
|
16
|
+
assert adapter.respond_to?(:logger), "The adapter should respond to #logger"
|
17
|
+
assert adapter.method('logger').arity == -2, "The adapter should accept two parameters for #logger, name and options hash"
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_adapter_mdc
|
21
|
+
assert adapter.respond_to?(:mdc), "The adapter should respond to #mdc"
|
22
|
+
assert adapter.method('mdc').arity == 0, "The adapter should accept no parameters for #mdc"
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_mdc_methods
|
26
|
+
mdc = adapter.mdc
|
27
|
+
assert mdc.respond_to?(:[]=), "The mdc should respond to #[]="
|
28
|
+
assert mdc.respond_to?(:[]), "The mdc should respond to #[]"
|
29
|
+
assert mdc.respond_to?(:delete), "The mdc should respond to #delete"
|
30
|
+
assert mdc.respond_to?(:clear), "The mdc should respond to #clear"
|
31
|
+
assert mdc.respond_to?(:to_hash), "The mdc should respond to #to_hash"
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_logger_methods
|
35
|
+
tempfile = Tempfile.new('lint')
|
36
|
+
logger = adapter.logger('lint', :to => tempfile.path)
|
37
|
+
%w{debug info warn error fatal}.each do |level|
|
38
|
+
assert logger.respond_to?(level), "The logger should respond to ##{level}"
|
39
|
+
assert logger.respond_to?("#{level}?"), "The logger should respond to ##{level}?"
|
40
|
+
end
|
41
|
+
ensure
|
42
|
+
tempfile.unlink
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
# Access the adapter, defined by <tt>@adapter</tt>.
|
48
|
+
def adapter
|
49
|
+
assert !!@adapter, "An adapter must be defined"
|
50
|
+
@adapter
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/loggr/slf4j.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module Loggr
|
2
|
+
module SLF4J
|
3
|
+
|
4
|
+
# Simple access to both SLF4J and Logback implementations, for testing
|
5
|
+
# and/or warbler integration.
|
6
|
+
#
|
7
|
+
module Jars
|
8
|
+
|
9
|
+
# Base dir, where the jar files reside, this is "lib/" ergo "lib/loggr/slf4j/jars.rb/../../../"
|
10
|
+
SLF4J_LIB_PATH = File.expand_path(File.dirname(File.dirname(File.dirname(__FILE__))))
|
11
|
+
|
12
|
+
|
13
|
+
# Path to SLF4J API
|
14
|
+
def slf4j_api_jar_path
|
15
|
+
@api_jar_path ||= Dir[File.join(SLF4J_LIB_PATH, 'slf4j-api-*.jar')].first
|
16
|
+
end
|
17
|
+
module_function :slf4j_api_jar_path
|
18
|
+
|
19
|
+
# Logback Core JAR
|
20
|
+
def logback_core_jar_path
|
21
|
+
@logback_core_jar_path ||= Dir[File.join(SLF4J_LIB_PATH, 'logback-core-*.jar')].first
|
22
|
+
end
|
23
|
+
module_function :logback_core_jar_path
|
24
|
+
|
25
|
+
# Logback Classic JAR
|
26
|
+
def logback_jar_path
|
27
|
+
@logback_jar_path ||= Dir[File.join(SLF4J_LIB_PATH, 'logback-classic-*.jar')].first
|
28
|
+
end
|
29
|
+
module_function :logback_jar_path
|
30
|
+
|
31
|
+
# Require all JARs, if `all` is set to `false`, then only
|
32
|
+
# the SLF4J API jar is loaded.
|
33
|
+
def require_slf4j_jars!(all = true)
|
34
|
+
require self.slf4j_api_jar_path
|
35
|
+
if all
|
36
|
+
require self.logback_core_jar_path
|
37
|
+
require self.logback_jar_path
|
38
|
+
end
|
39
|
+
end
|
40
|
+
module_function :require_slf4j_jars!
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'loggr/severity'
|
2
|
+
|
3
|
+
module Loggr
|
4
|
+
module SLF4J
|
5
|
+
|
6
|
+
# Simple marker factory which uses `org.slf4j.MarkerFactory`, but
|
7
|
+
# caches the result in a local ruby hash, by name.
|
8
|
+
class MarkerFactory
|
9
|
+
|
10
|
+
# Get marker for any non-empty string.
|
11
|
+
def self.[](name)
|
12
|
+
name = name.to_s.strip
|
13
|
+
return nil if name.length == 0
|
14
|
+
@markers ||= {}
|
15
|
+
@markers[name] ||= Java::OrgSlf4j::MarkerFactory.getMarker(name)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# A logger which is backed by SLF4J, thus only useable in a JRuby environment.
|
20
|
+
#
|
21
|
+
class Logger
|
22
|
+
|
23
|
+
# Get severities
|
24
|
+
include Loggr::Severity
|
25
|
+
|
26
|
+
# Basically has *no* impact, because is handled by SLF4J
|
27
|
+
attr_accessor :level
|
28
|
+
|
29
|
+
# Just to ensure compatiability with AS::BufferedLogger
|
30
|
+
attr_reader :auto_flushing, :flush, :close
|
31
|
+
|
32
|
+
# Access raw SLF4J logger & marker instances
|
33
|
+
attr_reader :java_logger, :java_marker
|
34
|
+
|
35
|
+
# Create a new Logger instance for the given name
|
36
|
+
#
|
37
|
+
def initialize(name, options = {})
|
38
|
+
name = self.class.in_java_notation(name)
|
39
|
+
@java_logger = Java::OrgSlf4j::LoggerFactory.getLogger(name.to_s)
|
40
|
+
@java_marker = MarkerFactory[options[:marker]]
|
41
|
+
|
42
|
+
# seriously, this is handled by slf4j and pretty dynamic
|
43
|
+
@level = Logger::UNKNOWN
|
44
|
+
@auto_flushing = true
|
45
|
+
end
|
46
|
+
|
47
|
+
# Create the logger methods via meta programming, sweet.
|
48
|
+
%w{trace debug info warn error}.each do |severity|
|
49
|
+
class_eval <<-EOT, __FILE__, __LINE__ + 1
|
50
|
+
def #{severity}(message = nil, progname = nil, &block) # def debug(message = nil, progname = nil, &block)
|
51
|
+
marker = (progname ? MarkerFactory[progname] : nil) || java_marker # marker = (progname ? MarkerFactory[progname] : nil) || java_marker
|
52
|
+
if java_logger.is_#{severity}_enabled(marker) # if java_logger.is_debug_enabled(marker)
|
53
|
+
java_logger.#{severity}(marker, build_message(message, progname, &block)) # java_logger.debug(marker, build_message(message, progname, &block))
|
54
|
+
end # end
|
55
|
+
end # end
|
56
|
+
|
57
|
+
def #{severity}? # def debug?
|
58
|
+
!!java_logger.is_#{severity}_enabled(java_marker) # !!java_logger.is_debug_enabled(java_marker)
|
59
|
+
end # end
|
60
|
+
EOT
|
61
|
+
end
|
62
|
+
|
63
|
+
# Add support for fatal, just alias to error
|
64
|
+
alias_method :fatal, :error
|
65
|
+
alias_method :fatal?, :error?
|
66
|
+
|
67
|
+
# If a class, module or object is used converts `Foo::Bar::SomeThing` to
|
68
|
+
# java notation: `foo.bar.SomeThing`. Symbols and Strings are left as is!
|
69
|
+
#
|
70
|
+
def self.in_java_notation(name)
|
71
|
+
return name.to_s if name.respond_to?(:to_str) || name.is_a?(Symbol)
|
72
|
+
name = name.is_a?(Module) ? name.name.to_s : name.class.name.to_s
|
73
|
+
parts = name.split('::')
|
74
|
+
last = parts.pop
|
75
|
+
parts.map { |p| p.downcase }.push(last).join('.')
|
76
|
+
end
|
77
|
+
|
78
|
+
protected
|
79
|
+
|
80
|
+
# Construct the message, note that progname will be ignored, maybe set as
|
81
|
+
# MDC?
|
82
|
+
def build_message(message = nil, progname = nil, &block)
|
83
|
+
message = yield if message.nil? && block_given?
|
84
|
+
message.to_s.gsub(/$\s*^/, '')
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|